/*-------------------------------------------------------------------------
 *
 * objectaddress.c
 *	  用于处理ObjectAddresses的函数
 *
 * Portions Copyright (c) 1996-2022, PostgreSQL Global Development Group
 * Portions Copyright (c) 1994, Regents of the University of California
 *
 *
 * IDENTIFICATION
 *	  src/backend/catalog/objectaddress.c
 *
 *-------------------------------------------------------------------------
 */

#include "postgres.h"

#include "access/genam.h"
#include "access/htup_details.h"
#include "access/relation.h"
#include "access/sysattr.h"
#include "access/table.h"
#include "catalog/catalog.h"
#include "catalog/objectaddress.h"
#include "catalog/pg_am.h"
#include "catalog/pg_amop.h"
#include "catalog/pg_amproc.h"
#include "catalog/pg_attrdef.h"
#include "catalog/pg_authid.h"
#include "catalog/pg_cast.h"
#include "catalog/pg_collation.h"
#include "catalog/pg_constraint.h"
#include "catalog/pg_conversion.h"
#include "catalog/pg_database.h"
#include "catalog/pg_default_acl.h"
#include "catalog/pg_enum.h"
#include "catalog/pg_event_trigger.h"
#include "catalog/pg_extension.h"
#include "catalog/pg_foreign_data_wrapper.h"
#include "catalog/pg_foreign_server.h"
#include "catalog/pg_language.h"
#include "catalog/pg_largeobject.h"
#include "catalog/pg_largeobject_metadata.h"
#include "catalog/pg_namespace.h"
#include "catalog/pg_opclass.h"
#include "catalog/pg_operator.h"
#include "catalog/pg_opfamily.h"
#include "catalog/pg_parameter_acl.h"
#include "catalog/pg_policy.h"
#include "catalog/pg_proc.h"
#include "catalog/pg_publication.h"
#include "catalog/pg_publication_namespace.h"
#include "catalog/pg_publication_rel.h"
#include "catalog/pg_rewrite.h"
#include "catalog/pg_statistic_ext.h"
#include "catalog/pg_subscription.h"
#include "catalog/pg_tablespace.h"
#include "catalog/pg_transform.h"
#include "catalog/pg_trigger.h"
#include "catalog/pg_ts_config.h"
#include "catalog/pg_ts_dict.h"
#include "catalog/pg_ts_parser.h"
#include "catalog/pg_ts_template.h"
#include "catalog/pg_type.h"
#include "catalog/pg_user_mapping.h"
#include "commands/dbcommands.h"
#include "commands/defrem.h"
#include "commands/event_trigger.h"
#include "commands/extension.h"
#include "commands/policy.h"
#include "commands/proclang.h"
#include "commands/tablespace.h"
#include "commands/trigger.h"
#include "foreign/foreign.h"
#include "funcapi.h"
#include "miscadmin.h"
#include "nodes/makefuncs.h"
#include "parser/parse_func.h"
#include "parser/parse_oper.h"
#include "parser/parse_type.h"
#include "rewrite/rewriteSupport.h"
#include "storage/large_object.h"
#include "storage/lmgr.h"
#include "storage/sinval.h"
#include "utils/acl.h"
#include "utils/builtins.h"
#include "utils/fmgroids.h"
#include "utils/lsyscache.h"
#include "utils/memutils.h"
#include "utils/regproc.h"
#include "utils/syscache.h"

/*
 * ObjectProperty
 *
 * 该数组提供系统对象结构的共同部分；以帮助
 * 整合处理各种对象类的例程。
 */
typedef struct
{
	const char *class_descr;	/* 描述目录的字符串，用于内部
								 * 错误消息 */
	Oid			class_oid;		/* 目录的oid */
	Oid			oid_index_oid;	/* 系统oid列上的索引oid */
	int			oid_catcache_id;	/* 系统oid列上的catcache ID */
	int			name_catcache_id;	/* (name,namespace)上的catcache ID，或
									 * 如果对象不在命名空间中，则为(name) */
	AttrNumber	attnum_oid;		/* oid列的属性编号 */
	AttrNumber	attnum_name;	/* name字段的attnum */
	AttrNumber	attnum_namespace;	/* namespace字段的attnum */
	AttrNumber	attnum_owner;	/* owner字段的attnum */
	AttrNumber	attnum_acl;		/* acl字段的attnum */
	ObjectType	objtype;		/* OBJECT_* 这个对象类型的 */
	bool		is_nsp_name_unique; /* nsp/name组合（如果没有命名空间，则仅为名称）是否可以被视为该类对象的唯一标识符？ */
} ObjectPropertyType;

static const ObjectPropertyType ObjectProperty[] =
{
	{
		"access method",
		AccessMethodRelationId,
		AmOidIndexId,
		AMOID,
		AMNAME,
		Anum_pg_am_oid,
		Anum_pg_am_amname,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		true
	},
	{
		"access method operator",
		AccessMethodOperatorRelationId,
		AccessMethodOperatorOidIndexId,
		-1,
		-1,
		Anum_pg_amop_oid,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		OBJECT_AMOP,
		false
	},
	{
		"access method procedure",
		AccessMethodProcedureRelationId,
		AccessMethodProcedureOidIndexId,
		-1,
		-1,
		Anum_pg_amproc_oid,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		OBJECT_AMPROC,
		false
	},
	{
		"cast",
		CastRelationId,
		CastOidIndexId,
		-1,
		-1,
		Anum_pg_cast_oid,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		false
	},
	{
		"collation",
		CollationRelationId,
		CollationOidIndexId,
		COLLOID,
		-1,						/* COLLNAMEENCNSP 也会考虑编码 */
		Anum_pg_collation_oid,
		Anum_pg_collation_collname,
		Anum_pg_collation_collnamespace,
		Anum_pg_collation_collowner,
		InvalidAttrNumber,
		OBJECT_COLLATION,
		true
	},
	{
		"constraint",
		ConstraintRelationId,
		ConstraintOidIndexId,
		CONSTROID,
		-1,
		Anum_pg_constraint_oid,
		Anum_pg_constraint_conname,
		Anum_pg_constraint_connamespace,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		false
	},
	{
		"conversion",
		ConversionRelationId,
		ConversionOidIndexId,
		CONVOID,
		CONNAMENSP,
		Anum_pg_conversion_oid,
		Anum_pg_conversion_conname,
		Anum_pg_conversion_connamespace,
		Anum_pg_conversion_conowner,
		InvalidAttrNumber,
		OBJECT_CONVERSION,
		true
	},
	{
		"database",
		DatabaseRelationId,
		DatabaseOidIndexId,
		DATABASEOID,
		-1,
		Anum_pg_database_oid,
		Anum_pg_database_datname,
		InvalidAttrNumber,
		Anum_pg_database_datdba,
		Anum_pg_database_datacl,
		OBJECT_DATABASE,
		true
	},
	{
		"default ACL",
		DefaultAclRelationId,
		DefaultAclOidIndexId,
		-1,
		-1,
		Anum_pg_default_acl_oid,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		OBJECT_DEFACL,
		false
	},
	{
		"extension",
		ExtensionRelationId,
		ExtensionOidIndexId,
		-1,
		-1,
		Anum_pg_extension_oid,
		Anum_pg_extension_extname,
		InvalidAttrNumber,		/* 扩展不属于 extnamespace */
		Anum_pg_extension_extowner,
		InvalidAttrNumber,
		OBJECT_EXTENSION,
		true
	},
	{
		"foreign-data wrapper",
		ForeignDataWrapperRelationId,
		ForeignDataWrapperOidIndexId,
		FOREIGNDATAWRAPPEROID,
		FOREIGNDATAWRAPPERNAME,
		Anum_pg_foreign_data_wrapper_oid,
		Anum_pg_foreign_data_wrapper_fdwname,
		InvalidAttrNumber,
		Anum_pg_foreign_data_wrapper_fdwowner,
		Anum_pg_foreign_data_wrapper_fdwacl,
		OBJECT_FDW,
		true
	},
	{
		"foreign server",
		ForeignServerRelationId,
		ForeignServerOidIndexId,
		FOREIGNSERVEROID,
		FOREIGNSERVERNAME,
		Anum_pg_foreign_server_oid,
		Anum_pg_foreign_server_srvname,
		InvalidAttrNumber,
		Anum_pg_foreign_server_srvowner,
		Anum_pg_foreign_server_srvacl,
		OBJECT_FOREIGN_SERVER,
		true
	},
	{
		"function",
		ProcedureRelationId,
		ProcedureOidIndexId,
		PROCOID,
		-1,						/* PROCNAMEARGSNSP 也考虑参数类型 */
		Anum_pg_proc_oid,
		Anum_pg_proc_proname,
		Anum_pg_proc_pronamespace,
		Anum_pg_proc_proowner,
		Anum_pg_proc_proacl,
		OBJECT_FUNCTION,
		false
	},
	{
		"language",
		LanguageRelationId,
		LanguageOidIndexId,
		LANGOID,
		LANGNAME,
		Anum_pg_language_oid,
		Anum_pg_language_lanname,
		InvalidAttrNumber,
		Anum_pg_language_lanowner,
		Anum_pg_language_lanacl,
		OBJECT_LANGUAGE,
		true
	},
	{
		"large object metadata",
		LargeObjectMetadataRelationId,
		LargeObjectMetadataOidIndexId,
		-1,
		-1,
		Anum_pg_largeobject_metadata_oid,
		InvalidAttrNumber,
		InvalidAttrNumber,
		Anum_pg_largeobject_metadata_lomowner,
		Anum_pg_largeobject_metadata_lomacl,
		OBJECT_LARGEOBJECT,
		false
	},
	{
		"operator class",
		OperatorClassRelationId,
		OpclassOidIndexId,
		CLAOID,
		-1,						/* CLAAMNAMENSP 也考虑 opcmethod */
		Anum_pg_opclass_oid,
		Anum_pg_opclass_opcname,
		Anum_pg_opclass_opcnamespace,
		Anum_pg_opclass_opcowner,
		InvalidAttrNumber,
		OBJECT_OPCLASS,
		true
	},
	{
		"operator",
		OperatorRelationId,
		OperatorOidIndexId,
		OPEROID,
		-1,						/* OPERNAMENSP 也考虑左型和右型 */
		Anum_pg_operator_oid,
		Anum_pg_operator_oprname,
		Anum_pg_operator_oprnamespace,
		Anum_pg_operator_oprowner,
		InvalidAttrNumber,
		OBJECT_OPERATOR,
		false
	},
	{
		"operator family",
		OperatorFamilyRelationId,
		OpfamilyOidIndexId,
		OPFAMILYOID,
		-1,						/* OPFAMILYAMNAMENSP 也考虑 opfmethod */
		Anum_pg_opfamily_oid,
		Anum_pg_opfamily_opfname,
		Anum_pg_opfamily_opfnamespace,
		Anum_pg_opfamily_opfowner,
		InvalidAttrNumber,
		OBJECT_OPFAMILY,
		true
	},
	{
		"role",
		AuthIdRelationId,
		AuthIdOidIndexId,
		AUTHOID,
		AUTHNAME,
		Anum_pg_authid_oid,
		Anum_pg_authid_rolname,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		true
	},
	{
		"rule",
		RewriteRelationId,
		RewriteOidIndexId,
		-1,
		-1,
		Anum_pg_rewrite_oid,
		Anum_pg_rewrite_rulename,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		false
	},
	{
		"schema",
		NamespaceRelationId,
		NamespaceOidIndexId,
		NAMESPACEOID,
		NAMESPACENAME,
		Anum_pg_namespace_oid,
		Anum_pg_namespace_nspname,
		InvalidAttrNumber,
		Anum_pg_namespace_nspowner,
		Anum_pg_namespace_nspacl,
		OBJECT_SCHEMA,
		true
	},
	{
		"relation",
		RelationRelationId,
		ClassOidIndexId,
		RELOID,
		RELNAMENSP,
		Anum_pg_class_oid,
		Anum_pg_class_relname,
		Anum_pg_class_relnamespace,
		Anum_pg_class_relowner,
		Anum_pg_class_relacl,
		OBJECT_TABLE,
		true
	},
	{
		"tablespace",
		TableSpaceRelationId,
		TablespaceOidIndexId,
		TABLESPACEOID,
		-1,
		Anum_pg_tablespace_oid,
		Anum_pg_tablespace_spcname,
		InvalidAttrNumber,
		Anum_pg_tablespace_spcowner,
		Anum_pg_tablespace_spcacl,
		OBJECT_TABLESPACE,
		true
	},
	{
		"transform",
		TransformRelationId,
		TransformOidIndexId,
		TRFOID,
		InvalidAttrNumber,
		Anum_pg_transform_oid
	},
	{
		"trigger",
		TriggerRelationId,
		TriggerOidIndexId,
		-1,
		-1,
		Anum_pg_trigger_oid,
		Anum_pg_trigger_tgname,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		false
	},
	{
		"policy",
		PolicyRelationId,
		PolicyOidIndexId,
		-1,
		-1,
		Anum_pg_policy_oid,
		Anum_pg_policy_polname,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		false
	},
	{
		"event trigger",
		EventTriggerRelationId,
		EventTriggerOidIndexId,
		EVENTTRIGGEROID,
		EVENTTRIGGERNAME,
		Anum_pg_event_trigger_oid,
		Anum_pg_event_trigger_evtname,
		InvalidAttrNumber,
		Anum_pg_event_trigger_evtowner,
		InvalidAttrNumber,
		OBJECT_EVENT_TRIGGER,
		true
	},
	{
		"text search configuration",
		TSConfigRelationId,
		TSConfigOidIndexId,
		TSCONFIGOID,
		TSCONFIGNAMENSP,
		Anum_pg_ts_config_oid,
		Anum_pg_ts_config_cfgname,
		Anum_pg_ts_config_cfgnamespace,
		Anum_pg_ts_config_cfgowner,
		InvalidAttrNumber,
		OBJECT_TSCONFIGURATION,
		true
	},
	{
		"text search dictionary",
		TSDictionaryRelationId,
		TSDictionaryOidIndexId,
		TSDICTOID,
		TSDICTNAMENSP,
		Anum_pg_ts_dict_oid,
		Anum_pg_ts_dict_dictname,
		Anum_pg_ts_dict_dictnamespace,
		Anum_pg_ts_dict_dictowner,
		InvalidAttrNumber,
		OBJECT_TSDICTIONARY,
		true
	},
	{
		"text search parser",
		TSParserRelationId,
		TSParserOidIndexId,
		TSPARSEROID,
		TSPARSERNAMENSP,
		Anum_pg_ts_parser_oid,
		Anum_pg_ts_parser_prsname,
		Anum_pg_ts_parser_prsnamespace,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		true
	},
	{
		"text search template",
		TSTemplateRelationId,
		TSTemplateOidIndexId,
		TSTEMPLATEOID,
		TSTEMPLATENAMENSP,
		Anum_pg_ts_template_oid,
		Anum_pg_ts_template_tmplname,
		Anum_pg_ts_template_tmplnamespace,
		InvalidAttrNumber,
		InvalidAttrNumber,
		-1,
		true,
	},
	{
		"type",
		TypeRelationId,
		TypeOidIndexId,
		TYPEOID,
		TYPENAMENSP,
		Anum_pg_type_oid,
		Anum_pg_type_typname,
		Anum_pg_type_typnamespace,
		Anum_pg_type_typowner,
		Anum_pg_type_typacl,
		OBJECT_TYPE,
		true
	},
	{
		"publication",
		PublicationRelationId,
		PublicationObjectIndexId,
		PUBLICATIONOID,
		PUBLICATIONNAME,
		Anum_pg_publication_oid,
		Anum_pg_publication_pubname,
		InvalidAttrNumber,
		Anum_pg_publication_pubowner,
		InvalidAttrNumber,
		OBJECT_PUBLICATION,
		true
	},
	{
		"subscription",
		SubscriptionRelationId,
		SubscriptionObjectIndexId,
		SUBSCRIPTIONOID,
		SUBSCRIPTIONNAME,
		Anum_pg_subscription_oid,
		Anum_pg_subscription_subname,
		InvalidAttrNumber,
		Anum_pg_subscription_subowner,
		InvalidAttrNumber,
		OBJECT_SUBSCRIPTION,
		true
	},
	{
		"extended statistics",
		StatisticExtRelationId,
		StatisticExtOidIndexId,
		STATEXTOID,
		STATEXTNAMENSP,
		Anum_pg_statistic_ext_oid,
		Anum_pg_statistic_ext_stxname,
		Anum_pg_statistic_ext_stxnamespace,
		Anum_pg_statistic_ext_stxowner,
		InvalidAttrNumber,		/* 没有 ACL（与关系相同） */
		OBJECT_STATISTIC_EXT,
		true
	},
	{
		"user mapping",
		UserMappingRelationId,
		UserMappingOidIndexId,
		USERMAPPINGOID,
		-1,
		Anum_pg_user_mapping_oid,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		InvalidAttrNumber,
		OBJECT_USER_MAPPING,
		false
	},
};

/*
 * 这个结构将由 getObjectTypeDescription 返回的字符串对象类型映射到 ObjectType 枚举值。请注意，一些枚举值可以通过不同的名称获得，并且某些字符串对象类型在输出枚举中没有相应的值。使用此映射的用户必须小心测试返回的无效值。
 *
 * 为了方便维护，这遵循 getObjectTypeDescription 的顺序。
 */
static const struct object_type_map
{
	const char *tm_name;
	ObjectType	tm_type;
}

			ObjectTypeMap[] =
{
	/* OCLASS_CLASS，所有类型的关系 */
	{
		"table", OBJECT_TABLE
	},
	{
		"index", OBJECT_INDEX
	},
	{
		"sequence", OBJECT_SEQUENCE
	},
	{
		"toast table", -1
	},							/* unmapped */
	{
		"view", OBJECT_VIEW
	},
	{
		"materialized view", OBJECT_MATVIEW
	},
	{
		"composite type", -1
	},							/* unmapped */
	{
		"foreign table", OBJECT_FOREIGN_TABLE
	},
	{
		"table column", OBJECT_COLUMN
	},
	{
		"index column", -1
	},							/* unmapped */
	{
		"sequence column", -1
	},							/* unmapped */
	{
		"toast table column", -1
	},							/* unmapped */
	{
		"view column", -1
	},							/* unmapped */
	{
		"materialized view column", -1
	},							/* unmapped */
	{
		"composite type column", -1
	},							/* unmapped */
	{
		"foreign table column", OBJECT_COLUMN
	},
	/* OCLASS_PROC */
	{
		"aggregate", OBJECT_AGGREGATE
	},
	{
		"function", OBJECT_FUNCTION
	},
	{
		"procedure", OBJECT_PROCEDURE
	},
	/* OCLASS_TYPE */
	{
		"type", OBJECT_TYPE
	},
	/* OCLASS_CAST */  
	{
		"cast", OBJECT_CAST
	},
	/* OCLASS_COLLATION */  
	{
		"collation", OBJECT_COLLATION
	},
	/* OCLASS_CONSTRAINT */  
	{
		"table constraint", OBJECT_TABCONSTRAINT
	},
	{
		"domain constraint", OBJECT_DOMCONSTRAINT
	},
	/* OCLASS_CONVERSION */  
	{
		"conversion", OBJECT_CONVERSION
	},
	/* OCLASS_DEFAULT */  
	{
		"default value", OBJECT_DEFAULT
	},
	/* OCLASS_LANGUAGE */  
	{
		"language", OBJECT_LANGUAGE
	},
	/* OCLASS_LARGEOBJECT */  
	{
		"large object", OBJECT_LARGEOBJECT
	},
	/* OCLASS_OPERATOR */  
	{
		"operator", OBJECT_OPERATOR
	},
	/* OCLASS_OPCLASS */  
	{
		"operator class", OBJECT_OPCLASS
	},
	/* OCLASS_OPFAMILY */  
	{
		"operator family", OBJECT_OPFAMILY
	},
	/* OCLASS_AM */  
	{
		"access method", OBJECT_ACCESS_METHOD
	},
	/* OCLASS_AMOP */  
	{
		"operator of access method", OBJECT_AMOP
	},
	/* OCLASS_AMPROC */  
	{
		"function of access method", OBJECT_AMPROC
	},
	/* OCLASS_REWRITE */  
	{
		"rule", OBJECT_RULE
	},
	/* OCLASS_TRIGGER */  
	{
		"trigger", OBJECT_TRIGGER
	},
	/* OCLASS_SCHEMA */  
	{
		"schema", OBJECT_SCHEMA
	},
	/* OCLASS_TSPARSER */  
	{
		"text search parser", OBJECT_TSPARSER
	},
	/* OCLASS_TSDICT */  
	{
		"text search dictionary", OBJECT_TSDICTIONARY
	},
	/* OCLASS_TSTEMPLATE */  
	{
		"text search template", OBJECT_TSTEMPLATE
	},
	/* OCLASS_TSCONFIG */  
	{
		"text search configuration", OBJECT_TSCONFIGURATION
	},
	/* OCLASS_ROLE */  
	{
		"role", OBJECT_ROLE
	},
	/* OCLASS_DATABASE */  
	{
		"database", OBJECT_DATABASE
	},
	/* OCLASS_TBLSPACE */  
	{
		"tablespace", OBJECT_TABLESPACE
	},
	/* OCLASS_FDW */  
	{
		"foreign-data wrapper", OBJECT_FDW
	},
	/* OCLASS_FOREIGN_SERVER */  
	{
		"server", OBJECT_FOREIGN_SERVER
	},
	/* OCLASS_USER_MAPPING */  
	{
		"user mapping", OBJECT_USER_MAPPING
	},
	/* OCLASS_DEFACL */  
	{
		"default acl", OBJECT_DEFACL
	},
	/* OCLASS_EXTENSION */  
	{
		"extension", OBJECT_EXTENSION
	},
	/* OCLASS_EVENT_TRIGGER */  
	{
		"event trigger", OBJECT_EVENT_TRIGGER
	},
	/* OCLASS_PARAMETER_ACL */  
	{
		"parameter ACL", OBJECT_PARAMETER_ACL
	},
	/* OCLASS_POLICY */  
	{
		"policy", OBJECT_POLICY
	},
	/* OCLASS_PUBLICATION */  
	{
		"publication", OBJECT_PUBLICATION
	},
	/* OCLASS_PUBLICATION_NAMESPACE */  
	{
		"publication namespace", OBJECT_PUBLICATION_NAMESPACE
	},
	/* OCLASS_PUBLICATION_REL */  
	{
		"publication relation", OBJECT_PUBLICATION_REL
	},
	/* OCLASS_SUBSCRIPTION */  
	{
		"subscription", OBJECT_SUBSCRIPTION
	},
	/* OCLASS_TRANSFORM */  
	{
		"transform", OBJECT_TRANSFORM
	},
	/* OCLASS_STATISTIC_EXT */  
	{
		"statistics object", OBJECT_STATISTIC_EXT
	}
};

const ObjectAddress InvalidObjectAddress =
{
	InvalidOid,
	InvalidOid,
	0
};

static ObjectAddress get_object_address_unqualified(ObjectType fc_objtype,
													String *fc_strval, bool fc_missing_ok);
static ObjectAddress get_relation_by_qualified_name(ObjectType fc_objtype,
													List *fc_object, Relation *fc_relp,
													LOCKMODE fc_lockmode, bool fc_missing_ok);
static ObjectAddress get_object_address_relobject(ObjectType fc_objtype,
												  List *fc_object, Relation *fc_relp, bool fc_missing_ok);
static ObjectAddress get_object_address_attribute(ObjectType fc_objtype,
												  List *fc_object, Relation *fc_relp,
												  LOCKMODE fc_lockmode, bool fc_missing_ok);
static ObjectAddress get_object_address_attrdef(ObjectType fc_objtype,
												List *fc_object, Relation *fc_relp, LOCKMODE fc_lockmode,
												bool fc_missing_ok);
static ObjectAddress get_object_address_type(ObjectType fc_objtype,
											 TypeName *fc_typename, bool fc_missing_ok);
static ObjectAddress get_object_address_opcf(ObjectType fc_objtype, List *fc_object,
											 bool fc_missing_ok);
static ObjectAddress get_object_address_opf_member(ObjectType fc_objtype,
												   List *fc_object, bool fc_missing_ok);

static ObjectAddress get_object_address_usermapping(List *fc_object,
													bool fc_missing_ok);
static ObjectAddress get_object_address_publication_rel(List *fc_object,
														Relation *fc_relp,
														bool fc_missing_ok);
static ObjectAddress get_object_address_publication_schema(List *fc_object,
														   bool fc_missing_ok);
static ObjectAddress get_object_address_defacl(List *fc_object,
											   bool fc_missing_ok);
static const ObjectPropertyType *get_object_property_data(Oid fc_class_id);

static void fc_getRelationDescription(StringInfo fc_buffer, Oid fc_relid,
								   bool fc_missing_ok);
static void fc_getOpFamilyDescription(StringInfo fc_buffer, Oid fc_opfid,
								   bool fc_missing_ok);
static void fc_getRelationTypeDescription(StringInfo fc_buffer, Oid fc_relid,
									   int32 fc_objectSubId, bool fc_missing_ok);
static void fc_getProcedureTypeDescription(StringInfo fc_buffer, Oid fc_procid,
										bool fc_missing_ok);
static void fc_getConstraintTypeDescription(StringInfo fc_buffer, Oid fc_constroid,
										 bool fc_missing_ok);
static void fc_getOpFamilyIdentity(StringInfo fc_buffer, Oid fc_opfid, List **fc_object,
								bool fc_missing_ok);
static void fc_getRelationIdentity(StringInfo fc_buffer, Oid fc_relid, List **fc_object,
								bool fc_missing_ok);

/*
 * 将对象名称和参数（由解析器传递）转换为 ObjectAddress。
 *
 * 返回的对象将使用指定的锁定模式进行锁定。如果查找的是子对象，则会锁定父对象。
 *
 * 如果对象是关系或关系的子对象（例如属性或约束），则关系也会被打开，*relp 接收打开的 relcache 条目指针；否则，*relp 被设置为 NULL。这有点麻烦，但它使生活更简单，因为调用者通常也需要 relcache 条目。调用者使用完后必须关闭 relcache 条目。当目标对象是关系本身或属性时，关系将使用指定的锁定模式进行锁定，但对于其他子对象，仅在关系上获取 AccessShareLock。
 *
 * 如果未找到对象，将抛出错误，除非 missing_ok 为 true。在这种情况下，不会获取锁，relp 被设置为 NULL，返回的地址的 objectId 设置为 InvalidOid。
 *
 * 我们当前不提供释放这里获取的锁的函数；通常，在提交之前必须保持锁，以防止并发删除操作。
 *
 * 注意：如果未找到对象，我们不会给出任何原因的指示。（如果名称是合格的，可能是缺少架构，或者在转换、函数或操作符的情况下类型名称不存在，等等）。目前只有一个调用者可能对此类信息感兴趣，因此我们在这里没有花费太多精力。如果更多调用者开始关心，最好在此函数中添加一些支持。
 */
ObjectAddress get_object_address(ObjectType fc_objtype, Node *fc_object,
				   Relation *fc_relp, LOCKMODE fc_lockmode, bool fc_missing_ok)
{
	ObjectAddress fc_address;
	ObjectAddress fc_old_address = {InvalidOid, InvalidOid, 0};
	Relation	fc_relation = NULL;
	uint64		fc_inval_count;

	/* 必须采取某种锁。 */
	Assert(fc_lockmode != NoLock);

	for (;;)
	{
		/*
		 * 记住这个值，以便在查找对象名称并锁定后，可以检查是否有任何无效消息被处理，这可能需要重做。
		 */
		fc_inval_count = SharedInvalidMessageCounter;

		/* 查找对象地址。 */
		switch (fc_objtype)
		{
			case OBJECT_INDEX:
			case OBJECT_SEQUENCE:
			case OBJECT_TABLE:
			case OBJECT_VIEW:
			case OBJECT_MATVIEW:
			case OBJECT_FOREIGN_TABLE:
				fc_address =
					get_relation_by_qualified_name(fc_objtype, castNode(List, fc_object),
												   &fc_relation, fc_lockmode,
												   fc_missing_ok);
				break;
			case OBJECT_COLUMN:
				fc_address =
					get_object_address_attribute(fc_objtype, castNode(List, fc_object),
												 &fc_relation, fc_lockmode,
												 fc_missing_ok);
				break;
			case OBJECT_DEFAULT:
				fc_address =
					get_object_address_attrdef(fc_objtype, castNode(List, fc_object),
											   &fc_relation, fc_lockmode,
											   fc_missing_ok);
				break;
			case OBJECT_RULE:
			case OBJECT_TRIGGER:
			case OBJECT_TABCONSTRAINT:
			case OBJECT_POLICY:
				fc_address = get_object_address_relobject(fc_objtype, castNode(List, fc_object),
													   &fc_relation, fc_missing_ok);
				break;
			case OBJECT_DOMCONSTRAINT:
				{
					List	   *fc_objlist;
					ObjectAddress fc_domaddr;
					char	   *fc_constrname;

					fc_objlist = castNode(List, fc_object);
					fc_domaddr = get_object_address_type(OBJECT_DOMAIN,
													  linitial_node(TypeName, fc_objlist),
													  fc_missing_ok);
					fc_constrname = strVal(lsecond(fc_objlist));

					fc_address.classId = ConstraintRelationId;
					fc_address.objectId = get_domain_constraint_oid(fc_domaddr.objectId,
																 fc_constrname, fc_missing_ok);
					fc_address.objectSubId = 0;
				}
				break;
			case OBJECT_DATABASE:
			case OBJECT_EXTENSION:
			case OBJECT_TABLESPACE:
			case OBJECT_ROLE:
			case OBJECT_SCHEMA:
			case OBJECT_LANGUAGE:
			case OBJECT_FDW:
			case OBJECT_FOREIGN_SERVER:
			case OBJECT_EVENT_TRIGGER:
			case OBJECT_PARAMETER_ACL:
			case OBJECT_ACCESS_METHOD:
			case OBJECT_PUBLICATION:
			case OBJECT_SUBSCRIPTION:
				fc_address = get_object_address_unqualified(fc_objtype,
														 castNode(String, fc_object), fc_missing_ok);
				break;
			case OBJECT_TYPE:
			case OBJECT_DOMAIN:
				fc_address = get_object_address_type(fc_objtype, castNode(TypeName, fc_object), fc_missing_ok);
				break;
			case OBJECT_AGGREGATE:
			case OBJECT_FUNCTION:
			case OBJECT_PROCEDURE:
			case OBJECT_ROUTINE:
				fc_address.classId = ProcedureRelationId;
				fc_address.objectId = LookupFuncWithArgs(fc_objtype, castNode(ObjectWithArgs, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_OPERATOR:
				fc_address.classId = OperatorRelationId;
				fc_address.objectId = LookupOperWithArgs(castNode(ObjectWithArgs, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_COLLATION:
				fc_address.classId = CollationRelationId;
				fc_address.objectId = get_collation_oid(castNode(List, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_CONVERSION:
				fc_address.classId = ConversionRelationId;
				fc_address.objectId = get_conversion_oid(castNode(List, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_OPCLASS:
			case OBJECT_OPFAMILY:
				fc_address = get_object_address_opcf(fc_objtype, castNode(List, fc_object), fc_missing_ok);
				break;
			case OBJECT_AMOP:
			case OBJECT_AMPROC:
				fc_address = get_object_address_opf_member(fc_objtype, castNode(List, fc_object), fc_missing_ok);
				break;
			case OBJECT_LARGEOBJECT:
				fc_address.classId = LargeObjectRelationId;
				fc_address.objectId = oidparse(fc_object);
				fc_address.objectSubId = 0;
				if (!LargeObjectExists(fc_address.objectId))
				{
					if (!fc_missing_ok)
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_OBJECT),
								 errmsg("large object %u does not exist",
										fc_address.objectId)));
				}
				break;
			case OBJECT_CAST:
				{
					TypeName   *fc_sourcetype = linitial_node(TypeName, castNode(List, fc_object));
					TypeName   *fc_targettype = lsecond_node(TypeName, castNode(List, fc_object));
					Oid			fc_sourcetypeid;
					Oid			fc_targettypeid;

					fc_sourcetypeid = LookupTypeNameOid(NULL, fc_sourcetype, fc_missing_ok);
					fc_targettypeid = LookupTypeNameOid(NULL, fc_targettype, fc_missing_ok);
					fc_address.classId = CastRelationId;
					fc_address.objectId =
						get_cast_oid(fc_sourcetypeid, fc_targettypeid, fc_missing_ok);
					fc_address.objectSubId = 0;
				}
				break;
			case OBJECT_TRANSFORM:
				{
					TypeName   *fc_typename = linitial_node(TypeName, castNode(List, fc_object));
					char	   *fc_langname = strVal(lsecond(castNode(List, fc_object)));
					Oid			fc_type_id = LookupTypeNameOid(NULL, fc_typename, fc_missing_ok);
					Oid			fc_lang_id = get_language_oid(fc_langname, fc_missing_ok);

					fc_address.classId = TransformRelationId;
					fc_address.objectId =
						get_transform_oid(fc_type_id, fc_lang_id, fc_missing_ok);
					fc_address.objectSubId = 0;
				}
				break;
			case OBJECT_TSPARSER:
				fc_address.classId = TSParserRelationId;
				fc_address.objectId = get_ts_parser_oid(castNode(List, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_TSDICTIONARY:
				fc_address.classId = TSDictionaryRelationId;
				fc_address.objectId = get_ts_dict_oid(castNode(List, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_TSTEMPLATE:
				fc_address.classId = TSTemplateRelationId;
				fc_address.objectId = get_ts_template_oid(castNode(List, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_TSCONFIGURATION:
				fc_address.classId = TSConfigRelationId;
				fc_address.objectId = get_ts_config_oid(castNode(List, fc_object), fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			case OBJECT_USER_MAPPING:
				fc_address = get_object_address_usermapping(castNode(List, fc_object),
														 fc_missing_ok);
				break;
			case OBJECT_PUBLICATION_NAMESPACE:
				fc_address = get_object_address_publication_schema(castNode(List, fc_object),
																fc_missing_ok);
				break;
			case OBJECT_PUBLICATION_REL:
				fc_address = get_object_address_publication_rel(castNode(List, fc_object),
															 &fc_relation,
															 fc_missing_ok);
				break;
			case OBJECT_DEFACL:
				fc_address = get_object_address_defacl(castNode(List, fc_object),
													fc_missing_ok);
				break;
			case OBJECT_STATISTIC_EXT:
				fc_address.classId = StatisticExtRelationId;
				fc_address.objectId = get_statistics_object_oid(castNode(List, fc_object),
															 fc_missing_ok);
				fc_address.objectSubId = 0;
				break;
			default:
				elog(ERROR, "unrecognized objtype: %d", (int) fc_objtype);
				/* 安抚编译器，以防它认为 elog 可能返回 */
				fc_address.classId = InvalidOid;
				fc_address.objectId = InvalidOid;
				fc_address.objectSubId = 0;
		}

		/*
		 * 如果我们无法找到提供的对象，则不返回锁定。
		 */
		if (!OidIsValid(fc_address.objectId))
		{
			Assert(fc_missing_ok);
			return fc_address;
		}

		/*
		 * 如果我们正在重试，请检查我们是否得到了与上次相同的答案。如果是这样，我们就完成了；如果没有，我们锁定了错误的东西，因此放弃我们的锁。
		 */
		if (OidIsValid(fc_old_address.classId))
		{
			if (fc_old_address.classId == fc_address.classId
				&& fc_old_address.objectId == fc_address.objectId
				&& fc_old_address.objectSubId == fc_address.objectSubId)
				break;
			if (fc_old_address.classId != RelationRelationId)
			{
				if (IsSharedRelation(fc_old_address.classId))
					UnlockSharedObject(fc_old_address.classId,
									   fc_old_address.objectId,
									   0, fc_lockmode);
				else
					UnlockDatabaseObject(fc_old_address.classId,
										 fc_old_address.objectId,
										 0, fc_lockmode);
			}
		}

		/*
		 * 如果我们处理的是关系或属性，则关系已经锁定。否则，我们现在锁定它。
		 */
		if (fc_address.classId != RelationRelationId)
		{
			if (IsSharedRelation(fc_address.classId))
				LockSharedObject(fc_address.classId, fc_address.objectId, 0,
								 fc_lockmode);
			else
				LockDatabaseObject(fc_address.classId, fc_address.objectId, 0,
								   fc_lockmode);
		}

		/*
		 * 此时，我们已经解析名称到 OID 并锁定了相应的数据库对象。 但是，可能在我们获得对象上的锁时，并发 DDL 已以某种方式修改了数据库，使得我们最初查找的名称不再解析到该 OID。
		 *
		 * 如果 (a) 没有处理任何共享无效消息，或者 (b) 我们在某处锁定了关系，我们可以确定这不是一个问题。该模块中所有的关系名称查找最终都使用 RangeVarGetRelid() 获取关系锁，而该函数可以防止我们在这里担心的相同类型的竞争条件。即使在操作约束、规则或触发器时，我们仍然在关系上获得 AccessShareLock，这足以冻结任何并发 DDL。
		 *
		 * 但是在所有其他情况下，我们查找的名称可能不再指向我们锁定的对象，因此我们重试查找并查看我们是否得到相同的答案。
		 */
		if (fc_inval_count == SharedInvalidMessageCounter || fc_relation != NULL)
			break;
		fc_old_address = fc_address;
	}

	/* 返回对象地址和关系。 */
	*fc_relp = fc_relation;
	return fc_address;
}


/*
 * 根据RangeVar和对象名称返回一个ObjectAddress。由RangeVar识别的关系名称将被附加到作为对象传递的（可能为空）列表前面。这对于查找依赖于关系的对象的ObjectAddress很有用。其他所有考虑与上面的get_object_address完全相同。
 */
ObjectAddress get_object_address_rv(ObjectType fc_objtype, RangeVar *fc_rel, List *fc_object,
					  Relation *fc_relp, LOCKMODE fc_lockmode,
					  bool fc_missing_ok)
{
	if (fc_rel)
	{
		fc_object = lcons(makeString(fc_rel->relname), fc_object);
		if (fc_rel->schemaname)
			fc_object = lcons(makeString(fc_rel->schemaname), fc_object);
		if (fc_rel->catalogname)
			fc_object = lcons(makeString(fc_rel->catalogname), fc_object);
	}

	return get_object_address(fc_objtype, (Node *) fc_object,
							  fc_relp, fc_lockmode, fc_missing_ok);
}

/*
 * 查找由不合格名称识别的对象类型的ObjectAddress。
 */
static ObjectAddress get_object_address_unqualified(ObjectType fc_objtype,
							   String *fc_strval, bool fc_missing_ok)
{
	const char *fc_name;
	ObjectAddress fc_address;

	fc_name = strVal(fc_strval);

	/* 将名称转换为OID。 */
	switch (fc_objtype)
	{
		case OBJECT_ACCESS_METHOD:
			fc_address.classId = AccessMethodRelationId;
			fc_address.objectId = get_am_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_DATABASE:
			fc_address.classId = DatabaseRelationId;
			fc_address.objectId = get_database_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_EXTENSION:
			fc_address.classId = ExtensionRelationId;
			fc_address.objectId = get_extension_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_TABLESPACE:
			fc_address.classId = TableSpaceRelationId;
			fc_address.objectId = get_tablespace_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_ROLE:
			fc_address.classId = AuthIdRelationId;
			fc_address.objectId = get_role_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_SCHEMA:
			fc_address.classId = NamespaceRelationId;
			fc_address.objectId = get_namespace_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_LANGUAGE:
			fc_address.classId = LanguageRelationId;
			fc_address.objectId = get_language_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_FDW:
			fc_address.classId = ForeignDataWrapperRelationId;
			fc_address.objectId = get_foreign_data_wrapper_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_FOREIGN_SERVER:
			fc_address.classId = ForeignServerRelationId;
			fc_address.objectId = get_foreign_server_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_EVENT_TRIGGER:
			fc_address.classId = EventTriggerRelationId;
			fc_address.objectId = get_event_trigger_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_PARAMETER_ACL:
			fc_address.classId = ParameterAclRelationId;
			fc_address.objectId = ParameterAclLookup(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_PUBLICATION:
			fc_address.classId = PublicationRelationId;
			fc_address.objectId = get_publication_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_SUBSCRIPTION:
			fc_address.classId = SubscriptionRelationId;
			fc_address.objectId = get_subscription_oid(fc_name, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		default:
			elog(ERROR, "unrecognized objtype: %d", (int) fc_objtype);
			/* 安抚编译器，因为它不知道elog不会返回 */
			fc_address.classId = InvalidOid;
			fc_address.objectId = InvalidOid;
			fc_address.objectSubId = 0;
	}

	return fc_address;
}

/*
 * 通过合格名称定位关系。
 */
static ObjectAddress get_relation_by_qualified_name(ObjectType fc_objtype, List *fc_object,
							   Relation *fc_relp, LOCKMODE fc_lockmode,
							   bool fc_missing_ok)
{
	Relation	fc_relation;
	ObjectAddress fc_address;

	fc_address.classId = RelationRelationId;
	fc_address.objectId = InvalidOid;
	fc_address.objectSubId = 0;

	fc_relation = relation_openrv_extended(makeRangeVarFromNameList(fc_object),
										fc_lockmode, fc_missing_ok);
	if (!fc_relation)
		return fc_address;

	switch (fc_objtype)
	{
		case OBJECT_INDEX:
			if (fc_relation->rd_rel->relkind != RELKIND_INDEX &&
				fc_relation->rd_rel->relkind != RELKIND_PARTITIONED_INDEX)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is not an index",
								RelationGetRelationName(fc_relation))));
			break;
		case OBJECT_SEQUENCE:
			if (fc_relation->rd_rel->relkind != RELKIND_SEQUENCE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is not a sequence",
								RelationGetRelationName(fc_relation))));
			break;
		case OBJECT_TABLE:
			if (fc_relation->rd_rel->relkind != RELKIND_RELATION &&
				fc_relation->rd_rel->relkind != RELKIND_PARTITIONED_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is not a table",
								RelationGetRelationName(fc_relation))));
			break;
		case OBJECT_VIEW:
			if (fc_relation->rd_rel->relkind != RELKIND_VIEW)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is not a view",
								RelationGetRelationName(fc_relation))));
			break;
		case OBJECT_MATVIEW:
			if (fc_relation->rd_rel->relkind != RELKIND_MATVIEW)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is not a materialized view",
								RelationGetRelationName(fc_relation))));
			break;
		case OBJECT_FOREIGN_TABLE:
			if (fc_relation->rd_rel->relkind != RELKIND_FOREIGN_TABLE)
				ereport(ERROR,
						(errcode(ERRCODE_WRONG_OBJECT_TYPE),
						 errmsg("\"%s\" is not a foreign table",
								RelationGetRelationName(fc_relation))));
			break;
		default:
			elog(ERROR, "unrecognized objtype: %d", (int) fc_objtype);
			break;
	}

	/* Done. */
	fc_address.objectId = RelationGetRelid(fc_relation);
	*fc_relp = fc_relation;

	return fc_address;
}

/*
 * 查找附属于关系的对象的对象地址。
 *
 * 请注意，我们对关系只采用AccessShareLock。我们无需从get_object_address()传递LOCKMODE，因为这是对象本身的锁定模式，而不是与之附加的关系。
 */
static ObjectAddress get_object_address_relobject(ObjectType fc_objtype, List *fc_object,
							 Relation *fc_relp, bool fc_missing_ok)
{
	ObjectAddress fc_address;
	Relation	fc_relation = NULL;
	int			fc_nnames;
	const char *fc_depname;
	List	   *fc_relname;
	Oid			fc_reloid;

	/* 提取依赖对象的名称。 */
	fc_depname = strVal(llast(fc_object));

	/* 将关系名称与依赖对象名称分开。 */
	fc_nnames = list_length(fc_object);
	if (fc_nnames < 2)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("must specify relation and object name")));

	/* 提取关系名称并打开关系。 */
	fc_relname = list_truncate(list_copy(fc_object), fc_nnames - 1);
	fc_relation = table_openrv_extended(makeRangeVarFromNameList(fc_relname),
									 AccessShareLock,
									 fc_missing_ok);

	fc_reloid = fc_relation ? RelationGetRelid(fc_relation) : InvalidOid;

	switch (fc_objtype)
	{
		case OBJECT_RULE:
			fc_address.classId = RewriteRelationId;
			fc_address.objectId = fc_relation ?
				get_rewrite_oid(fc_reloid, fc_depname, fc_missing_ok) : InvalidOid;
			fc_address.objectSubId = 0;
			break;
		case OBJECT_TRIGGER:
			fc_address.classId = TriggerRelationId;
			fc_address.objectId = fc_relation ?
				get_trigger_oid(fc_reloid, fc_depname, fc_missing_ok) : InvalidOid;
			fc_address.objectSubId = 0;
			break;
		case OBJECT_TABCONSTRAINT:
			fc_address.classId = ConstraintRelationId;
			fc_address.objectId = fc_relation ?
				get_relation_constraint_oid(fc_reloid, fc_depname, fc_missing_ok) :
				InvalidOid;
			fc_address.objectSubId = 0;
			break;
		case OBJECT_POLICY:
			fc_address.classId = PolicyRelationId;
			fc_address.objectId = fc_relation ?
				get_relation_policy_oid(fc_reloid, fc_depname, fc_missing_ok) :
				InvalidOid;
			fc_address.objectSubId = 0;
			break;
		default:
			elog(ERROR, "unrecognized objtype: %d", (int) fc_objtype);
	}

	/* 避免在未找到对象时泄漏 relcache。 */
	if (!OidIsValid(fc_address.objectId))
	{
		if (fc_relation != NULL)
			table_close(fc_relation, AccessShareLock);

		fc_relation = NULL;		/* 事故预防部门 */
		return fc_address;
	}

	/* Done. */
	*fc_relp = fc_relation;
	return fc_address;
}

/* 
 * 查找属性的 ObjectAddress。
 */
static ObjectAddress get_object_address_attribute(ObjectType fc_objtype, List *fc_object,
							 Relation *fc_relp, LOCKMODE fc_lockmode,
							 bool fc_missing_ok)
{
	ObjectAddress fc_address;
	List	   *fc_relname;
	Oid			fc_reloid;
	Relation	fc_relation;
	const char *fc_attname;
	AttrNumber	fc_attnum;

	/* 提取关系名称并打开关系。 */
	if (list_length(fc_object) < 2)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("column name must be qualified")));
	fc_attname = strVal(llast(fc_object));
	fc_relname = list_truncate(list_copy(fc_object), list_length(fc_object) - 1);
	/* XXX 这里没有 missing_ok 支持 */
	fc_relation = relation_openrv(makeRangeVarFromNameList(fc_relname), fc_lockmode);
	fc_reloid = RelationGetRelid(fc_relation);

	/* 查找属性并构造返回值。 */
	fc_attnum = get_attnum(fc_reloid, fc_attname);
	if (fc_attnum == InvalidAttrNumber)
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("column \"%s\" of relation \"%s\" does not exist",
							fc_attname, NameListToString(fc_relname))));

		fc_address.classId = RelationRelationId;
		fc_address.objectId = InvalidOid;
		fc_address.objectSubId = InvalidAttrNumber;
		relation_close(fc_relation, fc_lockmode);
		return fc_address;
	}

	fc_address.classId = RelationRelationId;
	fc_address.objectId = fc_reloid;
	fc_address.objectSubId = fc_attnum;

	*fc_relp = fc_relation;
	return fc_address;
}

/* 
 * 查找属性默认值的 ObjectAddress。
 */
static ObjectAddress get_object_address_attrdef(ObjectType fc_objtype, List *fc_object,
						   Relation *fc_relp, LOCKMODE fc_lockmode,
						   bool fc_missing_ok)
{
	ObjectAddress fc_address;
	List	   *fc_relname;
	Oid			fc_reloid;
	Relation	fc_relation;
	const char *fc_attname;
	AttrNumber	fc_attnum;
	TupleDesc	fc_tupdesc;
	Oid			fc_defoid;

	/* 提取关系名称并打开关系。 */
	if (list_length(fc_object) < 2)
		ereport(ERROR,
				(errcode(ERRCODE_SYNTAX_ERROR),
				 errmsg("column name must be qualified")));
	fc_attname = strVal(llast(fc_object));
	fc_relname = list_truncate(list_copy(fc_object), list_length(fc_object) - 1);
	/* XXX 这里没有 missing_ok 支持 */
	fc_relation = relation_openrv(makeRangeVarFromNameList(fc_relname), fc_lockmode);
	fc_reloid = RelationGetRelid(fc_relation);

	fc_tupdesc = RelationGetDescr(fc_relation);

	/* 查找属性编号并获取 pg_attrdef OID */
	fc_attnum = get_attnum(fc_reloid, fc_attname);
	fc_defoid = InvalidOid;
	if (fc_attnum != InvalidAttrNumber && fc_tupdesc->constr != NULL)
		fc_defoid = GetAttrDefaultOid(fc_reloid, fc_attnum);
	if (!OidIsValid(fc_defoid))
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_COLUMN),
					 errmsg("default value for column \"%s\" of relation \"%s\" does not exist",
							fc_attname, NameListToString(fc_relname))));

		fc_address.classId = AttrDefaultRelationId;
		fc_address.objectId = InvalidOid;
		fc_address.objectSubId = InvalidAttrNumber;
		relation_close(fc_relation, fc_lockmode);
		return fc_address;
	}

	fc_address.classId = AttrDefaultRelationId;
	fc_address.objectId = fc_defoid;
	fc_address.objectSubId = 0;

	*fc_relp = fc_relation;
	return fc_address;
}

/* 
 * 查找类型或域的 ObjectAddress
 */
static ObjectAddress get_object_address_type(ObjectType fc_objtype, TypeName *fc_typename, bool fc_missing_ok)
{
	ObjectAddress fc_address;
	Type		fc_tup;

	fc_address.classId = TypeRelationId;
	fc_address.objectId = InvalidOid;
	fc_address.objectSubId = 0;

	fc_tup = LookupTypeName(NULL, fc_typename, NULL, fc_missing_ok);
	if (!HeapTupleIsValid(fc_tup))
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("type \"%s\" does not exist",
							TypeNameToString(fc_typename))));
		return fc_address;
	}
	fc_address.objectId = typeTypeId(fc_tup);

	if (fc_objtype == OBJECT_DOMAIN)
	{
		if (((Form_pg_type) GETSTRUCT(fc_tup))->typtype != TYPTYPE_DOMAIN)
			ereport(ERROR,
					(errcode(ERRCODE_WRONG_OBJECT_TYPE),
					 errmsg("\"%s\" is not a domain",
							TypeNameToString(fc_typename))));
	}

	ReleaseSysCache(fc_tup);

	return fc_address;
}

/* 
 * 查找 opclass 或 opfamily 的 ObjectAddress。
 */
static ObjectAddress get_object_address_opcf(ObjectType fc_objtype, List *fc_object, bool fc_missing_ok)
{
	Oid			fc_amoid;
	ObjectAddress fc_address;

	/* XXX 这里没有 missing_ok 支持 */
	fc_amoid = get_index_am_oid(strVal(linitial(fc_object)), false);
	fc_object = list_copy_tail(fc_object, 1);

	switch (fc_objtype)
	{
		case OBJECT_OPCLASS:
			fc_address.classId = OperatorClassRelationId;
			fc_address.objectId = get_opclass_oid(fc_amoid, fc_object, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		case OBJECT_OPFAMILY:
			fc_address.classId = OperatorFamilyRelationId;
			fc_address.objectId = get_opfamily_oid(fc_amoid, fc_object, fc_missing_ok);
			fc_address.objectSubId = 0;
			break;
		default:
			elog(ERROR, "unrecognized objtype: %d", (int) fc_objtype);
			/* 安抚编译器，因为它不知道elog不会返回 */
			fc_address.classId = InvalidOid;
			fc_address.objectId = InvalidOid;
			fc_address.objectSubId = 0;
	}

	return fc_address;
}

/* 
 * 查找 opclass/opfamily 成员的 ObjectAddress。
 *
 * （返回的地址对应于一个 pg_amop/pg_amproc 对象）。
 */
static ObjectAddress get_object_address_opf_member(ObjectType fc_objtype,
							  List *fc_object, bool fc_missing_ok)
{
	ObjectAddress fc_famaddr;
	ObjectAddress fc_address;
	ListCell   *fc_cell;
	List	   *fc_copy;
	TypeName   *fc_typenames[2];
	Oid			fc_typeoids[2];
	int			fc_membernum;
	int			fc_i;

	/* 
	 * 对象列表的最后一个元素包含策略或过程
	 * 编号。我们需要在获取 opclass/family
	 * 地址之前将其剥离。其余部分可以直接由 get_object_address_opcf() 使用。
	 */
	fc_membernum = atoi(strVal(llast(linitial(fc_object))));
	fc_copy = list_truncate(list_copy(linitial(fc_object)), list_length(linitial(fc_object)) - 1);

	/* 这里没有 missing_ok 支持 */
	fc_famaddr = get_object_address_opcf(OBJECT_OPFAMILY, fc_copy, false);

	/* 找出左/右类型名称和 OID */
	fc_typenames[0] = fc_typenames[1] = NULL;
	fc_typeoids[0] = fc_typeoids[1] = InvalidOid;
	fc_i = 0;
	foreach(fc_cell, lsecond(fc_object))
	{
		ObjectAddress fc_typaddr;

		fc_typenames[fc_i] = lfirst_node(TypeName, fc_cell);
		fc_typaddr = get_object_address_type(OBJECT_TYPE, fc_typenames[fc_i], fc_missing_ok);
		fc_typeoids[fc_i] = fc_typaddr.objectId;
		if (++fc_i >= 2)
			break;
	}

	switch (fc_objtype)
	{
		case OBJECT_AMOP:
			{
				HeapTuple	fc_tp;

				ObjectAddressSet(fc_address, AccessMethodOperatorRelationId,
								 InvalidOid);

				fc_tp = SearchSysCache4(AMOPSTRATEGY,
									 ObjectIdGetDatum(fc_famaddr.objectId),
									 ObjectIdGetDatum(fc_typeoids[0]),
									 ObjectIdGetDatum(fc_typeoids[1]),
									 Int16GetDatum(fc_membernum));
				if (!HeapTupleIsValid(fc_tp))
				{
					if (!fc_missing_ok)
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_OBJECT),
								 errmsg("operator %d (%s, %s) of %s does not exist",
										fc_membernum,
										TypeNameToString(fc_typenames[0]),
										TypeNameToString(fc_typenames[1]),
										getObjectDescription(&fc_famaddr, false))));
				}
				else
				{
					fc_address.objectId = ((Form_pg_amop) GETSTRUCT(fc_tp))->oid;
					ReleaseSysCache(fc_tp);
				}
			}
			break;

		case OBJECT_AMPROC:
			{
				HeapTuple	fc_tp;

				ObjectAddressSet(fc_address, AccessMethodProcedureRelationId,
								 InvalidOid);

				fc_tp = SearchSysCache4(AMPROCNUM,
									 ObjectIdGetDatum(fc_famaddr.objectId),
									 ObjectIdGetDatum(fc_typeoids[0]),
									 ObjectIdGetDatum(fc_typeoids[1]),
									 Int16GetDatum(fc_membernum));
				if (!HeapTupleIsValid(fc_tp))
				{
					if (!fc_missing_ok)
						ereport(ERROR,
								(errcode(ERRCODE_UNDEFINED_OBJECT),
								 errmsg("function %d (%s, %s) of %s does not exist",
										fc_membernum,
										TypeNameToString(fc_typenames[0]),
										TypeNameToString(fc_typenames[1]),
										getObjectDescription(&fc_famaddr, false))));
				}
				else
				{
					fc_address.objectId = ((Form_pg_amproc) GETSTRUCT(fc_tp))->oid;
					ReleaseSysCache(fc_tp);
				}
			}
			break;
		default:
			elog(ERROR, "unrecognized objtype: %d", (int) fc_objtype);
	}

	return fc_address;
}

/* 
 * 查找用户映射的 ObjectAddress。
 */
static ObjectAddress get_object_address_usermapping(List *fc_object, bool fc_missing_ok)
{
	ObjectAddress fc_address;
	Oid			fc_userid;
	char	   *fc_username;
	char	   *fc_servername;
	ForeignServer *fc_server;
	HeapTuple	fc_tp;

	ObjectAddressSet(fc_address, UserMappingRelationId, InvalidOid);

	/* 从输入列表中获取字符串名称，以便用于错误消息 */
	fc_username = strVal(linitial(fc_object));
	fc_servername = strVal(lsecond(fc_object));

	/* 查找映射用户的 pg_authid OID；如果是 PUBLIC 则为 InvalidOid */
	if (strcmp(fc_username, "public") == 0)
		fc_userid = InvalidOid;
	else
	{
		fc_tp = SearchSysCache1(AUTHNAME,
							 CStringGetDatum(fc_username));
		if (!HeapTupleIsValid(fc_tp))
		{
			if (!fc_missing_ok)
				ereport(ERROR,
						(errcode(ERRCODE_UNDEFINED_OBJECT),
						 errmsg("user mapping for user \"%s\" on server \"%s\" does not exist",
								fc_username, fc_servername)));
			return fc_address;
		}
		fc_userid = ((Form_pg_authid) GETSTRUCT(fc_tp))->oid;
		ReleaseSysCache(fc_tp);
	}

	/* 现在查找 pg_user_mapping 元组 */
	fc_server = GetForeignServerByName(fc_servername, true);
	if (!fc_server)
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("server \"%s\" does not exist", fc_servername)));
		return fc_address;
	}
	fc_tp = SearchSysCache2(USERMAPPINGUSERSERVER,
						 ObjectIdGetDatum(fc_userid),
						 ObjectIdGetDatum(fc_server->serverid));
	if (!HeapTupleIsValid(fc_tp))
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("user mapping for user \"%s\" on server \"%s\" does not exist",
							fc_username, fc_servername)));
		return fc_address;
	}

	fc_address.objectId = ((Form_pg_user_mapping) GETSTRUCT(fc_tp))->oid;

	ReleaseSysCache(fc_tp);

	return fc_address;
}

/* 
 * 查找发布关系的 ObjectAddress。对象参数的第一个元素是
 * 关系名称，第二个是发布名称。
 */
static ObjectAddress get_object_address_publication_rel(List *fc_object,
								   Relation *fc_relp, bool fc_missing_ok)
{
	ObjectAddress fc_address;
	Relation	fc_relation;
	List	   *fc_relname;
	char	   *fc_pubname;
	Publication *fc_pub;

	ObjectAddressSet(fc_address, PublicationRelRelationId, InvalidOid);

	fc_relname = linitial(fc_object);
	fc_relation = relation_openrv_extended(makeRangeVarFromNameList(fc_relname),
										AccessShareLock, fc_missing_ok);
	if (!fc_relation)
		return fc_address;

	/* 从输入列表中获取发布名称 */
	fc_pubname = strVal(lsecond(fc_object));

	/* 现在查找 pg_publication 元组 */
	fc_pub = GetPublicationByName(fc_pubname, fc_missing_ok);
	if (!fc_pub)
	{
		relation_close(fc_relation, AccessShareLock);
		return fc_address;
	}

	/* 在 syscache 中查找发布关系映射。 */
	fc_address.objectId =
		GetSysCacheOid2(PUBLICATIONRELMAP, Anum_pg_publication_rel_oid,
						ObjectIdGetDatum(RelationGetRelid(fc_relation)),
						ObjectIdGetDatum(fc_pub->oid));
	if (!OidIsValid(fc_address.objectId))
	{
		if (!fc_missing_ok)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("publication relation \"%s\" in publication \"%s\" does not exist",
							RelationGetRelationName(fc_relation), fc_pubname)));
		relation_close(fc_relation, AccessShareLock);
		return fc_address;
	}

	*fc_relp = fc_relation;
	return fc_address;
}

/* 
 * 查找发布模式的 ObjectAddress。对象参数的第一个元素是
 * 模式名称，第二个是发布名称。
 */
static ObjectAddress get_object_address_publication_schema(List *fc_object, bool fc_missing_ok)
{
	ObjectAddress fc_address;
	Publication *fc_pub;
	char	   *fc_pubname;
	char	   *fc_schemaname;
	Oid			fc_schemaid;

	ObjectAddressSet(fc_address, PublicationNamespaceRelationId, InvalidOid);

	/* 从输入列表中获取模式名称和发布名称 */
	fc_schemaname = strVal(linitial(fc_object));
	fc_pubname = strVal(lsecond(fc_object));

	fc_schemaid = get_namespace_oid(fc_schemaname, fc_missing_ok);
	if (!OidIsValid(fc_schemaid))
		return fc_address;

	/* 现在查找 pg_publication 元组 */
	fc_pub = GetPublicationByName(fc_pubname, fc_missing_ok);
	if (!fc_pub)
		return fc_address;

	/* 在 syscache 中查找发布模式映射 */
	fc_address.objectId =
		GetSysCacheOid2(PUBLICATIONNAMESPACEMAP,
						Anum_pg_publication_namespace_oid,
						ObjectIdGetDatum(fc_schemaid),
						ObjectIdGetDatum(fc_pub->oid));
	if (!OidIsValid(fc_address.objectId) && !fc_missing_ok)
		ereport(ERROR,
				(errcode(ERRCODE_UNDEFINED_OBJECT),
				 errmsg("publication schema \"%s\" in publication \"%s\" does not exist",
						fc_schemaname, fc_pubname)));

	return fc_address;
}

/* 
 * 查找默认 ACL 的 ObjectAddress。
 */
static ObjectAddress get_object_address_defacl(List *fc_object, bool fc_missing_ok)
{
	HeapTuple	fc_tp;
	Oid			fc_userid;
	Oid			fc_schemaid;
	char	   *fc_username;
	char	   *fc_schema;
	char		fc_objtype;
	char	   *fc_objtype_str;
	ObjectAddress fc_address;

	ObjectAddressSet(fc_address, DefaultAclRelationId, InvalidOid);

	/* 
	 * 首先弄清楚文本属性，以便可以用于
	 * 错误报告。
	 */
	fc_username = strVal(lsecond(fc_object));
	if (list_length(fc_object) >= 3)
		fc_schema = (char *) strVal(lthird(fc_object));
	else
		fc_schema = NULL;

	/* 
	 * 解码 defaclobjtype。只考虑第一个字符；其余字符串（如果有）将被无视。
	 */
	fc_objtype = ((char *) strVal(linitial(fc_object)))[0];
	switch (fc_objtype)
	{
		case DEFACLOBJ_RELATION:
			fc_objtype_str = "tables";
			break;
		case DEFACLOBJ_SEQUENCE:
			fc_objtype_str = "sequences";
			break;
		case DEFACLOBJ_FUNCTION:
			fc_objtype_str = "functions";
			break;
		case DEFACLOBJ_TYPE:
			fc_objtype_str = "types";
			break;
		case DEFACLOBJ_NAMESPACE:
			fc_objtype_str = "schemas";
			break;
		default:
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("unrecognized default ACL object type \"%c\"", fc_objtype),
					 errhint("Valid object types are \"%c\", \"%c\", \"%c\", \"%c\", \"%c\".",
							 DEFACLOBJ_RELATION,
							 DEFACLOBJ_SEQUENCE,
							 DEFACLOBJ_FUNCTION,
							 DEFACLOBJ_TYPE,
							 DEFACLOBJ_NAMESPACE)));
	}

	/* 
	 * 查找用户 ID。如果用户不存在，则表现为“未找到默认 ACL”。
	 */
	fc_tp = SearchSysCache1(AUTHNAME,
						 CStringGetDatum(fc_username));
	if (!HeapTupleIsValid(fc_tp))
		goto not_found;
	fc_userid = ((Form_pg_authid) GETSTRUCT(fc_tp))->oid;
	ReleaseSysCache(fc_tp);

	/* 
	 * 如果给出了模式名称，查找其 OID。如果不存在，
	 * 则表现为“未找到默认 ACL”。
	 */
	if (fc_schema)
	{
		fc_schemaid = get_namespace_oid(fc_schema, true);
		if (fc_schemaid == InvalidOid)
			goto not_found;
	}
	else
		fc_schemaid = InvalidOid;

	/* 最后，查找 pg_default_acl 对象 */
	fc_tp = SearchSysCache3(DEFACLROLENSPOBJ,
						 ObjectIdGetDatum(fc_userid),
						 ObjectIdGetDatum(fc_schemaid),
						 CharGetDatum(fc_objtype));
	if (!HeapTupleIsValid(fc_tp))
		goto not_found;

	fc_address.objectId = ((Form_pg_default_acl) GETSTRUCT(fc_tp))->oid;
	ReleaseSysCache(fc_tp);

	return fc_address;

not_found:
	if (!fc_missing_ok)
	{
		if (fc_schema)
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("default ACL for user \"%s\" in schema \"%s\" on %s does not exist",
							fc_username, fc_schema, fc_objtype_str)));
		else
			ereport(ERROR,
					(errcode(ERRCODE_UNDEFINED_OBJECT),
					 errmsg("default ACL for user \"%s\" on %s does not exist",
							fc_username, fc_objtype_str)));
	}
	return fc_address;
}

/* 将一个TEXT数组转换为由解析器发出的字符串值列表，这就是get_object_address使用的输入。 */
static List * fc_textarray_to_strvaluelist(ArrayType *fc_arr)
{
	Datum	   *fc_elems;
	bool	   *fc_nulls;
	int			fc_nelems;
	List	   *fc_list = NIL;
	int			fc_i;

	deconstruct_array(fc_arr, TEXTOID, -1, false, TYPALIGN_INT,
					  &fc_elems, &fc_nulls, &fc_nelems);

	for (fc_i = 0; fc_i < fc_nelems; fc_i++)
	{
		if (fc_nulls[fc_i])
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("name or argument lists may not contain nulls")));
		fc_list = lappend(fc_list, makeString(TextDatumGetCString(fc_elems[fc_i])));
	}

	return fc_list;
}

/* SQL可调用版本的get_object_address */
Datum pg_get_object_address(PG_FUNCTION_ARGS)
{
	char	   *fc_ttype = TextDatumGetCString(PG_GETARG_DATUM(0));
	ArrayType  *fc_namearr = PG_GETARG_ARRAYTYPE_P(1);
	ArrayType  *fc_argsarr = PG_GETARG_ARRAYTYPE_P(2);
	int			fc_itype;
	ObjectType	fc_type;
	List	   *fc_name = NIL;
	TypeName   *fc_typename = NULL;
	List	   *fc_args = NIL;
	Node	   *fc_objnode = NULL;
	ObjectAddress fc_addr;
	TupleDesc	fc_tupdesc;
	Datum		fc_values[3];
	bool		fc_nulls[3];
	HeapTuple	fc_htup;
	Relation	fc_relation;

	/* 解码对象类型，如果未知则引发错误 */
	fc_itype = read_objtype_from_string(fc_ttype);
	if (fc_itype < 0)
		ereport(ERROR,
				(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
				 errmsg("unsupported object type \"%s\"", fc_ttype)));
	fc_type = (ObjectType) fc_itype;

	/* 将文本数组转换为适合给定对象类型的表示。大多数使用简单的字符串值列表，但也有一些例外。 */
	if (fc_type == OBJECT_TYPE || fc_type == OBJECT_DOMAIN || fc_type == OBJECT_CAST ||
		fc_type == OBJECT_TRANSFORM || fc_type == OBJECT_DOMCONSTRAINT)
	{
		Datum	   *fc_elems;
		bool	   *fc_nulls;
		int			fc_nelems;

		deconstruct_array(fc_namearr, TEXTOID, -1, false, TYPALIGN_INT,
						  &fc_elems, &fc_nulls, &fc_nelems);
		if (fc_nelems != 1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("name list length must be exactly %d", 1)));
		if (fc_nulls[0])
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("name or argument lists may not contain nulls")));
		fc_typename = typeStringToTypeName(TextDatumGetCString(fc_elems[0]));
	}
	else if (fc_type == OBJECT_LARGEOBJECT)
	{
		Datum	   *fc_elems;
		bool	   *fc_nulls;
		int			fc_nelems;

		deconstruct_array(fc_namearr, TEXTOID, -1, false, TYPALIGN_INT,
						  &fc_elems, &fc_nulls, &fc_nelems);
		if (fc_nelems != 1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("name list length must be exactly %d", 1)));
		if (fc_nulls[0])
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("large object OID may not be null")));
		fc_objnode = (Node *) makeFloat(TextDatumGetCString(fc_elems[0]));
	}
	else
	{
		fc_name = fc_textarray_to_strvaluelist(fc_namearr);
		if (list_length(fc_name) < 1)
			ereport(ERROR,
					(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
					 errmsg("name list length must be at least %d", 1)));
	}

	/* 如果提供了参数，按对象类型解码它们。 */
	if (fc_type == OBJECT_AGGREGATE ||
		fc_type == OBJECT_FUNCTION ||
		fc_type == OBJECT_PROCEDURE ||
		fc_type == OBJECT_ROUTINE ||
		fc_type == OBJECT_OPERATOR ||
		fc_type == OBJECT_CAST ||
		fc_type == OBJECT_AMOP ||
		fc_type == OBJECT_AMPROC)
	{
		/* 在这些情况下，参数列表必须是TypeName */
		Datum	   *fc_elems;
		bool	   *fc_nulls;
		int			fc_nelems;
		int			fc_i;

		deconstruct_array(fc_argsarr, TEXTOID, -1, false, TYPALIGN_INT,
						  &fc_elems, &fc_nulls, &fc_nelems);

		fc_args = NIL;
		for (fc_i = 0; fc_i < fc_nelems; fc_i++)
		{
			if (fc_nulls[fc_i])
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("name or argument lists may not contain nulls")));
			fc_args = lappend(fc_args,
						   typeStringToTypeName(TextDatumGetCString(fc_elems[fc_i])));
		}
	}
	else
	{
		/* 对于所有其他对象类型，请使用字符串值 */
		fc_args = fc_textarray_to_strvaluelist(fc_argsarr);
	}

	/* get_object_address对输入列表的长度非常敏感；检查它们是否符合要求。 */
	switch (fc_type)
	{
		case OBJECT_PUBLICATION_NAMESPACE:
		case OBJECT_USER_MAPPING:
			if (list_length(fc_name) != 1)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("name list length must be exactly %d", 1)));
			/* fall through to check args length */
			/* FALLTHROUGH */
		case OBJECT_DOMCONSTRAINT:
		case OBJECT_CAST:
		case OBJECT_PUBLICATION_REL:
		case OBJECT_DEFACL:
		case OBJECT_TRANSFORM:
			if (list_length(fc_args) != 1)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("argument list length must be exactly %d", 1)));
			break;
		case OBJECT_OPFAMILY:
		case OBJECT_OPCLASS:
			if (list_length(fc_name) < 2)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("name list length must be at least %d", 2)));
			break;
		case OBJECT_AMOP:
		case OBJECT_AMPROC:
			if (list_length(fc_name) < 3)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("name list length must be at least %d", 3)));
			/* fall through to check args length */
			/* FALLTHROUGH */
		case OBJECT_OPERATOR:
			if (list_length(fc_args) != 2)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("argument list length must be exactly %d", 2)));
			break;
		default:
			break;
	}

	/* 现在构建get_object_address()期望的给定类型的节点类型。 */
	switch (fc_type)
	{
		case OBJECT_TABLE:
		case OBJECT_SEQUENCE:
		case OBJECT_VIEW:
		case OBJECT_MATVIEW:
		case OBJECT_INDEX:
		case OBJECT_FOREIGN_TABLE:
		case OBJECT_COLUMN:
		case OBJECT_ATTRIBUTE:
		case OBJECT_COLLATION:
		case OBJECT_CONVERSION:
		case OBJECT_STATISTIC_EXT:
		case OBJECT_TSPARSER:
		case OBJECT_TSDICTIONARY:
		case OBJECT_TSTEMPLATE:
		case OBJECT_TSCONFIGURATION:
		case OBJECT_DEFAULT:
		case OBJECT_POLICY:
		case OBJECT_RULE:
		case OBJECT_TRIGGER:
		case OBJECT_TABCONSTRAINT:
		case OBJECT_OPCLASS:
		case OBJECT_OPFAMILY:
			fc_objnode = (Node *) fc_name;
			break;
		case OBJECT_ACCESS_METHOD:
		case OBJECT_DATABASE:
		case OBJECT_EVENT_TRIGGER:
		case OBJECT_EXTENSION:
		case OBJECT_FDW:
		case OBJECT_FOREIGN_SERVER:
		case OBJECT_LANGUAGE:
		case OBJECT_PARAMETER_ACL:
		case OBJECT_PUBLICATION:
		case OBJECT_ROLE:
		case OBJECT_SCHEMA:
		case OBJECT_SUBSCRIPTION:
		case OBJECT_TABLESPACE:
			if (list_length(fc_name) != 1)
				ereport(ERROR,
						(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
						 errmsg("name list length must be exactly %d", 1)));
			fc_objnode = linitial(fc_name);
			break;
		case OBJECT_TYPE:
		case OBJECT_DOMAIN:
			fc_objnode = (Node *) fc_typename;
			break;
		case OBJECT_CAST:
		case OBJECT_DOMCONSTRAINT:
		case OBJECT_TRANSFORM:
			fc_objnode = (Node *) list_make2(fc_typename, linitial(fc_args));
			break;
		case OBJECT_PUBLICATION_REL:
			fc_objnode = (Node *) list_make2(fc_name, linitial(fc_args));
			break;
		case OBJECT_PUBLICATION_NAMESPACE:
		case OBJECT_USER_MAPPING:
			fc_objnode = (Node *) list_make2(linitial(fc_name), linitial(fc_args));
			break;
		case OBJECT_DEFACL:
			fc_objnode = (Node *) lcons(linitial(fc_args), fc_name);
			break;
		case OBJECT_AMOP:
		case OBJECT_AMPROC:
			fc_objnode = (Node *) list_make2(fc_name, fc_args);
			break;
		case OBJECT_FUNCTION:
		case OBJECT_PROCEDURE:
		case OBJECT_ROUTINE:
		case OBJECT_AGGREGATE:
		case OBJECT_OPERATOR:
			{
				ObjectWithArgs *fc_owa = makeNode(ObjectWithArgs);

				fc_owa->objname = fc_name;
				fc_owa->objargs = fc_args;
				fc_objnode = (Node *) fc_owa;
				break;
			}
		case OBJECT_LARGEOBJECT:
			/* 已在上面处理 */
			break;
			/* 没有默认值，让编译器警告缺少情况 */
	}

	if (fc_objnode == NULL)
		elog(ERROR, "unrecognized object type: %d", fc_type);

	fc_addr = get_object_address(fc_type, fc_objnode,
							  &fc_relation, AccessShareLock, false);

	/* 我们不需要 relcache 条目，感谢您 */
	if (fc_relation)
		relation_close(fc_relation, AccessShareLock);

	fc_tupdesc = CreateTemplateTupleDesc(3);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "classid",
					   OIDOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "objid",
					   OIDOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "objsubid",
					   INT4OID, -1, 0);
	fc_tupdesc = BlessTupleDesc(fc_tupdesc);

	fc_values[0] = ObjectIdGetDatum(fc_addr.classId);
	fc_values[1] = ObjectIdGetDatum(fc_addr.objectId);
	fc_values[2] = Int32GetDatum(fc_addr.objectSubId);
	fc_nulls[0] = false;
	fc_nulls[1] = false;
	fc_nulls[2] = false;

	fc_htup = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	PG_RETURN_DATUM(HeapTupleGetDatum(fc_htup));
}

/*
 * 检查由 get_object_address 先前识别的对象的所有权。
 */
void check_object_ownership(Oid fc_roleid, ObjectType fc_objtype, ObjectAddress fc_address,
					   Node *fc_object, Relation fc_relation)
{
	switch (fc_objtype)
	{
		case OBJECT_INDEX:
		case OBJECT_SEQUENCE:
		case OBJECT_TABLE:
		case OBJECT_VIEW:
		case OBJECT_MATVIEW:
		case OBJECT_FOREIGN_TABLE:
		case OBJECT_COLUMN:
		case OBJECT_RULE:
		case OBJECT_TRIGGER:
		case OBJECT_POLICY:
		case OBJECT_TABCONSTRAINT:
			if (!pg_class_ownercheck(RelationGetRelid(fc_relation), fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   RelationGetRelationName(fc_relation));
			break;
		case OBJECT_DATABASE:
			if (!pg_database_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_TYPE:
		case OBJECT_DOMAIN:
		case OBJECT_ATTRIBUTE:
			if (!pg_type_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_address.objectId);
			break;
		case OBJECT_DOMCONSTRAINT:
			{
				HeapTuple	fc_tuple;
				Oid			fc_contypid;

				fc_tuple = SearchSysCache1(CONSTROID,
										ObjectIdGetDatum(fc_address.objectId));
				if (!HeapTupleIsValid(fc_tuple))
					elog(ERROR, "constraint with OID %u does not exist",
						 fc_address.objectId);

				fc_contypid = ((Form_pg_constraint) GETSTRUCT(fc_tuple))->contypid;

				ReleaseSysCache(fc_tuple);

				/*
				 * 在这种情况下回退到类型所有权检查，因为这是域约束依赖的内容。
				 */
				if (!pg_type_ownercheck(fc_contypid, fc_roleid))
					aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_contypid);
			}
			break;
		case OBJECT_AGGREGATE:
		case OBJECT_FUNCTION:
		case OBJECT_PROCEDURE:
		case OBJECT_ROUTINE:
			if (!pg_proc_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString((castNode(ObjectWithArgs, fc_object))->objname));
			break;
		case OBJECT_OPERATOR:
			if (!pg_oper_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString((castNode(ObjectWithArgs, fc_object))->objname));
			break;
		case OBJECT_SCHEMA:
			if (!pg_namespace_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_COLLATION:
			if (!pg_collation_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString(castNode(List, fc_object)));
			break;
		case OBJECT_CONVERSION:
			if (!pg_conversion_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString(castNode(List, fc_object)));
			break;
		case OBJECT_EXTENSION:
			if (!pg_extension_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_FDW:
			if (!pg_foreign_data_wrapper_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_FOREIGN_SERVER:
			if (!pg_foreign_server_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_EVENT_TRIGGER:
			if (!pg_event_trigger_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_LANGUAGE:
			if (!pg_language_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_OPCLASS:
			if (!pg_opclass_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString(castNode(List, fc_object)));
			break;
		case OBJECT_OPFAMILY:
			if (!pg_opfamily_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString(castNode(List, fc_object)));
			break;
		case OBJECT_LARGEOBJECT:
			if (!lo_compat_privileges &&
				!pg_largeobject_ownercheck(fc_address.objectId, fc_roleid))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("must be owner of large object %u",
								fc_address.objectId)));
			break;
		case OBJECT_CAST:
			{
				
/* 我们只能检查源/目标类型的权限 */
				TypeName   *fc_sourcetype = linitial_node(TypeName, castNode(List, fc_object));
				TypeName   *fc_targettype = lsecond_node(TypeName, castNode(List, fc_object));
				Oid			fc_sourcetypeid = typenameTypeId(NULL, fc_sourcetype);
				Oid			fc_targettypeid = typenameTypeId(NULL, fc_targettype);

				if (!pg_type_ownercheck(fc_sourcetypeid, fc_roleid)
					&& !pg_type_ownercheck(fc_targettypeid, fc_roleid))
					ereport(ERROR,
							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
							 errmsg("must be owner of type %s or type %s",
									format_type_be(fc_sourcetypeid),
									format_type_be(fc_targettypeid))));
			}
			break;
		case OBJECT_PUBLICATION:
			if (!pg_publication_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_SUBSCRIPTION:
			if (!pg_subscription_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_TRANSFORM:
			{
				TypeName   *fc_typename = linitial_node(TypeName, castNode(List, fc_object));
				Oid			fc_typeid = typenameTypeId(NULL, fc_typename);

				if (!pg_type_ownercheck(fc_typeid, fc_roleid))
					aclcheck_error_type(ACLCHECK_NOT_OWNER, fc_typeid);
			}
			break;
		case OBJECT_TABLESPACE:
			if (!pg_tablespace_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   strVal(fc_object));
			break;
		case OBJECT_TSDICTIONARY:
			if (!pg_ts_dict_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString(castNode(List, fc_object)));
			break;
		case OBJECT_TSCONFIGURATION:
			if (!pg_ts_config_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString(castNode(List, fc_object)));
			break;
		case OBJECT_ROLE:

			/*
			 * 我们将角色视为由拥有 CREATEROLE 权限的人 "拥有"， 
			 * 除非超级用户仅由超级用户拥有。
			 */
			if (superuser_arg(fc_address.objectId))
			{
				if (!superuser_arg(fc_roleid))
					ereport(ERROR,
							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
							 errmsg("must be superuser")));
			}
			else
			{
				if (!has_createrole_privilege(fc_roleid))
					ereport(ERROR,
							(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
							 errmsg("must have CREATEROLE privilege")));
			}
			break;
		case OBJECT_TSPARSER:
		case OBJECT_TSTEMPLATE:
		case OBJECT_ACCESS_METHOD:
		case OBJECT_PARAMETER_ACL:
			/* 我们将这些对象类型视为由超级用户拥有 */
			if (!superuser_arg(fc_roleid))
				ereport(ERROR,
						(errcode(ERRCODE_INSUFFICIENT_PRIVILEGE),
						 errmsg("must be superuser")));
			break;
		case OBJECT_STATISTIC_EXT:
			if (!pg_statistics_object_ownercheck(fc_address.objectId, fc_roleid))
				aclcheck_error(ACLCHECK_NOT_OWNER, fc_objtype,
							   NameListToString(castNode(List, fc_object)));
			break;
		default:
			elog(ERROR, "unrecognized object type: %d",
				 (int) fc_objtype);
	}
}

/*
 * get_object_namespace
 *
 * 查找包含指定对象的架构。对于非架构对象，
 * 此函数返回 InvalidOid。
 */
Oid get_object_namespace(const ObjectAddress *fc_address)
{
	int			fc_cache;
	HeapTuple	fc_tuple;
	bool		fc_isnull;
	Oid			fc_oid;
	const ObjectPropertyType *fc_property;

	/* 如果不属于命名空间，则返回 InvalidOid。 */
	fc_property = get_object_property_data(fc_address->classId);
	if (fc_property->attnum_namespace == InvalidAttrNumber)
		return InvalidOid;

	/* 当前，我们只能处理具有系统缓存的对象类型。 */
	fc_cache = fc_property->oid_catcache_id;
	Assert(fc_cache != -1);

	/* 从 syscache 获取元组并提取命名空间属性。 */
	fc_tuple = SearchSysCache1(fc_cache, ObjectIdGetDatum(fc_address->objectId));
	if (!HeapTupleIsValid(fc_tuple))
		elog(ERROR, "cache lookup failed for cache %d oid %u",
			 fc_cache, fc_address->objectId);
	fc_oid = DatumGetObjectId(SysCacheGetAttr(fc_cache,
										   fc_tuple,
										   fc_property->attnum_namespace,
										   &fc_isnull));
	Assert(!fc_isnull);
	ReleaseSysCache(fc_tuple);

	return fc_oid;
}

/*
 * 返回给定对象类型的 ObjectType，如 getObjectTypeDescription 所示；
 * 如果没有有效的 ObjectType 代码存在，但它是 getObjectTypeDescription
 * 的可能输出类型，则返回 -1。否则，将抛出错误。
 */
int read_objtype_from_string(const char *fc_objtype)
{
	int			fc_i;

	for (fc_i = 0; fc_i < lengthof(ObjectTypeMap); fc_i++)
	{
		if (strcmp(ObjectTypeMap[fc_i].tm_name, fc_objtype) == 0)
			return ObjectTypeMap[fc_i].tm_type;
	}
	ereport(ERROR,
			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
			 errmsg("unrecognized object type \"%s\"", fc_objtype)));

	return -1;					/* 保持编译器安静 */
}

/*
 * 引用 ObjectPropertyType 字段的接口
 */
const char * get_object_class_descr(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->class_descr;
}

Oid get_object_oid_index(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->oid_index_oid;
}

int get_object_catcache_oid(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->oid_catcache_id;
}

int get_object_catcache_name(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->name_catcache_id;
}

AttrNumber get_object_attnum_oid(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->attnum_oid;
}

AttrNumber get_object_attnum_name(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->attnum_name;
}

AttrNumber get_object_attnum_namespace(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->attnum_namespace;
}

AttrNumber get_object_attnum_owner(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->attnum_owner;
}

AttrNumber get_object_attnum_acl(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->attnum_acl;
}

/*
 * get_object_type
 *
 * 返回与给定对象关联的对象类型。此例程主要用于确定在 ACL 
 * 检查错误消息中提及的对象类型，因此希望它避免失败。
 */
ObjectType
get_object_type(Oid fc_class_id, Oid fc_object_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	if (fc_prop->objtype == OBJECT_TABLE)
	{
		/*
		 * 如果属性数据表示它是一个表，则深入挖掘以获取真实的关系类型，
		 * 以便调用者能生成更精确的错误消息。
		 */
		return get_relkind_objtype(get_rel_relkind(fc_object_id));
	}
	else
		return fc_prop->objtype;
}

bool get_object_namensp_unique(Oid fc_class_id)
{
	const ObjectPropertyType *fc_prop = get_object_property_data(fc_class_id);

	return fc_prop->is_nsp_name_unique;
}

/*
 * 返回我们是否对 ObjectProperty 表中的给定对象类有有用的数据。
 */
bool is_objectclass_supported(Oid fc_class_id)
{
	int			fc_index;

	for (fc_index = 0; fc_index < lengthof(ObjectProperty); fc_index++)
	{
		if (ObjectProperty[fc_index].class_oid == fc_class_id)
			return true;
	}

	return false;
}

/*
 * 通过 class_id 查找 ObjectProperty 结构。
 */
static const ObjectPropertyType *
get_object_property_data(Oid fc_class_id)
{
	static const ObjectPropertyType *fc_prop_last = NULL;
	int			fc_index;

	/*
	 * 加快对特定对象类多个连续查找的捷径。
	 */
	if (fc_prop_last && fc_prop_last->class_oid == fc_class_id)
		return fc_prop_last;

	for (fc_index = 0; fc_index < lengthof(ObjectProperty); fc_index++)
	{
		if (ObjectProperty[fc_index].class_oid == fc_class_id)
		{
			fc_prop_last = &ObjectProperty[fc_index];
			return &ObjectProperty[fc_index];
		}
	}

	ereport(ERROR,
			(errmsg_internal("unrecognized class ID: %u", fc_class_id)));

	return NULL;				/* 保持 MSC 编译器快乐 */
}

/*
 * 返回给定 OID 的对象在给定目录中的元组副本（必须由调用者打开并适当锁定）。
 * 如果未找到 OID，则返回 NULL。
 *
 * 我们首先尝试使用 syscache（如果可用）。
 */
HeapTuple get_catalog_object_by_oid(Relation fc_catalog, AttrNumber fc_oidcol, Oid fc_objectId)
{
	return
		get_catalog_object_by_oid_extended(fc_catalog, fc_oidcol, fc_objectId, false);
}

/*
 * 与get_catalog_object_by_oid()相同，但多了一个“locktup”参数
 * 控制是否以InplaceUpdateTupleLock模式获取LOCKTAG_TUPLE。
 * 请参见README.tuplock部分“锁定以写入就地更新的表”。
 */
HeapTuple get_catalog_object_by_oid_extended(Relation fc_catalog,
								   AttrNumber fc_oidcol,
								   Oid fc_objectId,
								   bool fc_locktup)
{
	HeapTuple	fc_tuple;
	Oid			fc_classId = RelationGetRelid(fc_catalog);
	int			fc_oidCacheId = get_object_catcache_oid(fc_classId);

	if (fc_oidCacheId > 0)
	{
		if (fc_locktup)
			fc_tuple = SearchSysCacheLockedCopy1(fc_oidCacheId,
											  ObjectIdGetDatum(fc_objectId));
		else
			fc_tuple = SearchSysCacheCopy1(fc_oidCacheId,
										ObjectIdGetDatum(fc_objectId));
		if (!HeapTupleIsValid(fc_tuple))	/* 不应该发生 */
			return NULL;
	}
	else
	{
		Oid			fc_oidIndexId = get_object_oid_index(fc_classId);
		SysScanDesc fc_scan;
		ScanKeyData fc_skey;

		Assert(OidIsValid(fc_oidIndexId));

		ScanKeyInit(&fc_skey,
					fc_oidcol,
					BTEqualStrategyNumber, F_OIDEQ,
					ObjectIdGetDatum(fc_objectId));

		fc_scan = systable_beginscan(fc_catalog, fc_oidIndexId, true,
								  NULL, 1, &fc_skey);
		fc_tuple = systable_getnext(fc_scan);
		if (!HeapTupleIsValid(fc_tuple))
		{
			systable_endscan(fc_scan);
			return NULL;
		}

		if (fc_locktup)
			LockTuple(fc_catalog, &fc_tuple->t_self, InplaceUpdateTupleLock);

		fc_tuple = heap_copytuple(fc_tuple);

		systable_endscan(fc_scan);
	}

	return fc_tuple;
}

/*
 * getPublicationSchemaInfo
 *
 * 从对象地址获取发布名称和模式名称到pubname和nspname。
 * pubname和nspname都是palloc分配的字符串，将由调用者释放。
 */
static bool fc_getPublicationSchemaInfo(const ObjectAddress *fc_object, bool fc_missing_ok,
						 char **fc_pubname, char **fc_nspname)
{
	HeapTuple	fc_tup;
	Form_pg_publication_namespace fc_pnform;

	fc_tup = SearchSysCache1(PUBLICATIONNAMESPACE,
						  ObjectIdGetDatum(fc_object->objectId));
	if (!HeapTupleIsValid(fc_tup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for publication schema %u",
				 fc_object->objectId);
		return false;
	}

	fc_pnform = (Form_pg_publication_namespace) GETSTRUCT(fc_tup);
	*fc_pubname = get_publication_name(fc_pnform->pnpubid, fc_missing_ok);
	if (!(*fc_pubname))
	{
		ReleaseSysCache(fc_tup);
		return false;
	}

	*fc_nspname = get_namespace_name(fc_pnform->pnnspid);
	if (!(*fc_nspname))
	{
		Oid			fc_schemaid = fc_pnform->pnnspid;

		pfree(*fc_pubname);
		ReleaseSysCache(fc_tup);
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for schema %u",
				 fc_schemaid);
		return false;
	}

	ReleaseSysCache(fc_tup);
	return true;
}

/*
 * getObjectDescription：构建消息的对象描述
 *
 * 结果是一个palloc分配的字符串。如果missing_ok为true，则
 * 对于未定义的对象返回NULL，否则会产生错误。
 */
char * getObjectDescription(const ObjectAddress *fc_object, bool fc_missing_ok)
{
	StringInfoData fc_buffer;

	initStringInfo(&fc_buffer);

	switch (getObjectClass(fc_object))
	{
		case OCLASS_CLASS:
			if (fc_object->objectSubId == 0)
				fc_getRelationDescription(&fc_buffer, fc_object->objectId, fc_missing_ok);
			else
			{
				/* 列，而不是整个关系 */
				StringInfoData fc_rel;
				char	   *fc_attname = get_attname(fc_object->objectId,
												  fc_object->objectSubId,
												  fc_missing_ok);

				if (!fc_attname)
					break;

				initStringInfo(&fc_rel);
				fc_getRelationDescription(&fc_rel, fc_object->objectId, fc_missing_ok);
				/* 翻译者：第二个%s是，例如，“表%s” */
				appendStringInfo(&fc_buffer, _("column %s of %s"),
								 fc_attname, fc_rel.data);
				pfree(fc_rel.data);
			}
			break;

		case OCLASS_PROC:
			{
				bits16		fc_flags = FORMAT_PROC_INVALID_AS_NULL;
				char	   *fc_proname = format_procedure_extended(fc_object->objectId,
																fc_flags);

				if (fc_proname == NULL)
					break;

				appendStringInfo(&fc_buffer, _("function %s"), fc_proname);
				break;
			}

		case OCLASS_TYPE:
			{
				bits16		fc_flags = FORMAT_TYPE_INVALID_AS_NULL;
				char	   *fc_typname = format_type_extended(fc_object->objectId, -1,
														   fc_flags);

				if (fc_typname == NULL)
					break;

				appendStringInfo(&fc_buffer, _("type %s"), fc_typname);
				break;
			}

		case OCLASS_CAST:
			{
				Relation	fc_castDesc;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_rcscan;
				HeapTuple	fc_tup;
				Form_pg_cast fc_castForm;

				fc_castDesc = table_open(CastRelationId, AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_cast_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_rcscan = systable_beginscan(fc_castDesc, CastOidIndexId, true,
											NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_rcscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for cast %u",
							 fc_object->objectId);

					systable_endscan(fc_rcscan);
					table_close(fc_castDesc, AccessShareLock);
					break;
				}

				fc_castForm = (Form_pg_cast) GETSTRUCT(fc_tup);

				appendStringInfo(&fc_buffer, _("cast from %s to %s"),
								 format_type_be(fc_castForm->castsource),
								 format_type_be(fc_castForm->casttarget));

				systable_endscan(fc_rcscan);
				table_close(fc_castDesc, AccessShareLock);
				break;
			}

		case OCLASS_COLLATION:
			{
				HeapTuple	fc_collTup;
				Form_pg_collation fc_coll;
				char	   *fc_nspname;

				fc_collTup = SearchSysCache1(COLLOID,
										  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_collTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for collation %u",
							 fc_object->objectId);
					break;
				}

				fc_coll = (Form_pg_collation) GETSTRUCT(fc_collTup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (CollationIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_coll->collnamespace);

				appendStringInfo(&fc_buffer, _("collation %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_coll->collname)));
				ReleaseSysCache(fc_collTup);
				break;
			}

		case OCLASS_CONSTRAINT:
			{
				HeapTuple	fc_conTup;
				Form_pg_constraint fc_con;

				fc_conTup = SearchSysCache1(CONSTROID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_conTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for constraint %u",
							 fc_object->objectId);
					break;
				}

				fc_con = (Form_pg_constraint) GETSTRUCT(fc_conTup);

				if (OidIsValid(fc_con->conrelid))
				{
					StringInfoData fc_rel;

					initStringInfo(&fc_rel);
					fc_getRelationDescription(&fc_rel, fc_con->conrelid, false);
					/* 翻译者：第二个%s是，例如，“表%s” */
					appendStringInfo(&fc_buffer, _("constraint %s on %s"),
									 NameStr(fc_con->conname), fc_rel.data);
					pfree(fc_rel.data);
				}
				else
				{
					appendStringInfo(&fc_buffer, _("constraint %s"),
									 NameStr(fc_con->conname));
				}

				ReleaseSysCache(fc_conTup);
				break;
			}

		case OCLASS_CONVERSION:
			{
				HeapTuple	fc_conTup;
				Form_pg_conversion fc_conv;
				char	   *fc_nspname;

				fc_conTup = SearchSysCache1(CONVOID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_conTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for conversion %u",
							 fc_object->objectId);
					break;
				}

				fc_conv = (Form_pg_conversion) GETSTRUCT(fc_conTup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (ConversionIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_conv->connamespace);

				appendStringInfo(&fc_buffer, _("conversion %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_conv->conname)));
				ReleaseSysCache(fc_conTup);
				break;
			}

		case OCLASS_DEFAULT:
			{
				ObjectAddress fc_colobject;

				fc_colobject = GetAttrDefaultColumnAddress(fc_object->objectId);

				if (!OidIsValid(fc_colobject.objectId))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for attrdef %u",
							 fc_object->objectId);
					break;
				}

				/* 翻译者：%s通常是“表%s的列%s” */
				appendStringInfo(&fc_buffer, _("default value for %s"),
								 getObjectDescription(&fc_colobject, false));
				break;
			}

		case OCLASS_LANGUAGE:
			{
				char	   *fc_langname = get_language_name(fc_object->objectId,
														 fc_missing_ok);

				if (fc_langname)
					appendStringInfo(&fc_buffer, _("language %s"),
									 get_language_name(fc_object->objectId, false));
				break;
			}

		case OCLASS_LARGEOBJECT:
			if (!LargeObjectExists(fc_object->objectId))
				break;
			appendStringInfo(&fc_buffer, _("large object %u"),
							 fc_object->objectId);
			break;

		case OCLASS_OPERATOR:
			{
				bits16		fc_flags = FORMAT_OPERATOR_INVALID_AS_NULL;
				char	   *fc_oprname = format_operator_extended(fc_object->objectId,
															   fc_flags);

				if (fc_oprname == NULL)
					break;

				appendStringInfo(&fc_buffer, _("operator %s"), fc_oprname);
				break;
			}

		case OCLASS_OPCLASS:
			{
				HeapTuple	fc_opcTup;
				Form_pg_opclass fc_opcForm;
				HeapTuple	fc_amTup;
				Form_pg_am	fc_amForm;
				char	   *fc_nspname;

				fc_opcTup = SearchSysCache1(CLAOID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_opcTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for opclass %u",
							 fc_object->objectId);
					break;
				}

				fc_opcForm = (Form_pg_opclass) GETSTRUCT(fc_opcTup);

				fc_amTup = SearchSysCache1(AMOID,
										ObjectIdGetDatum(fc_opcForm->opcmethod));
				if (!HeapTupleIsValid(fc_amTup))
					elog(ERROR, "cache lookup failed for access method %u",
						 fc_opcForm->opcmethod);
				fc_amForm = (Form_pg_am) GETSTRUCT(fc_amTup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (OpclassIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_opcForm->opcnamespace);

				appendStringInfo(&fc_buffer, _("operator class %s for access method %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_opcForm->opcname)),
								 NameStr(fc_amForm->amname));

				ReleaseSysCache(fc_amTup);
				ReleaseSysCache(fc_opcTup);
				break;
			}

		case OCLASS_OPFAMILY:
			fc_getOpFamilyDescription(&fc_buffer, fc_object->objectId, fc_missing_ok);
			break;

		case OCLASS_AM:
			{
				HeapTuple	fc_tup;

				fc_tup = SearchSysCache1(AMOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for access method %u",
							 fc_object->objectId);
					break;
				}

				appendStringInfo(&fc_buffer, _("access method %s"),
								 NameStr(((Form_pg_am) GETSTRUCT(fc_tup))->amname));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_AMOP:
			{
				Relation	fc_amopDesc;
				HeapTuple	fc_tup;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_amscan;
				Form_pg_amop fc_amopForm;
				StringInfoData fc_opfam;

				fc_amopDesc = table_open(AccessMethodOperatorRelationId,
									  AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_amop_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_amscan = systable_beginscan(fc_amopDesc, AccessMethodOperatorOidIndexId, true,
											NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_amscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for amop entry %u",
							 fc_object->objectId);

					systable_endscan(fc_amscan);
					table_close(fc_amopDesc, AccessShareLock);
					break;
				}

				fc_amopForm = (Form_pg_amop) GETSTRUCT(fc_tup);

				initStringInfo(&fc_opfam);
				fc_getOpFamilyDescription(&fc_opfam, fc_amopForm->amopfamily, false);

				/*
				 * 我们在这里使用FORMAT_TYPE_ALLOW_INVALID，以便在
				 * 类型链接悬挂时不会完全失败，这是一种形式
				 * 的目录损坏，可能由于旧的bugs而发生。
				 */

				/*------
				   翻译者：%d是操作符策略（一个数字），前两个%s是数据类型名称，
				   第三个%s是操作符族的描述，最后一个%s是带参数的
				   操作符的文本形式。  */
				appendStringInfo(&fc_buffer, _("operator %d (%s, %s) of %s: %s"),
								 fc_amopForm->amopstrategy,
								 format_type_extended(fc_amopForm->amoplefttype,
													  -1, FORMAT_TYPE_ALLOW_INVALID),
								 format_type_extended(fc_amopForm->amoprighttype,
													  -1, FORMAT_TYPE_ALLOW_INVALID),
								 fc_opfam.data,
								 format_operator(fc_amopForm->amopopr));

				pfree(fc_opfam.data);

				systable_endscan(fc_amscan);
				table_close(fc_amopDesc, AccessShareLock);
				break;
			}

		case OCLASS_AMPROC:
			{
				Relation	fc_amprocDesc;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_amscan;
				HeapTuple	fc_tup;
				Form_pg_amproc fc_amprocForm;
				StringInfoData fc_opfam;

				fc_amprocDesc = table_open(AccessMethodProcedureRelationId,
										AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_amproc_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_amscan = systable_beginscan(fc_amprocDesc, AccessMethodProcedureOidIndexId, true,
											NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_amscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for amproc entry %u",
							 fc_object->objectId);

					systable_endscan(fc_amscan);
					table_close(fc_amprocDesc, AccessShareLock);
					break;
				}

				fc_amprocForm = (Form_pg_amproc) GETSTRUCT(fc_tup);

				initStringInfo(&fc_opfam);
				fc_getOpFamilyDescription(&fc_opfam, fc_amprocForm->amprocfamily, false);

				/*
				 * 我们在这里使用FORMAT_TYPE_ALLOW_INVALID，以便在
				 * 类型链接悬挂时不会完全失败，这是一种形式
				 * 的目录损坏，可能由于旧的bugs而发生。
				 */

				/*------
				   翻译者：%d是函数编号，前两个%s是数据类型名称，
				   第三个%s是操作符族的描述，最后一个%s是带参数的
				   函数的文本形式。  */
				appendStringInfo(&fc_buffer, _("function %d (%s, %s) of %s: %s"),
								 fc_amprocForm->amprocnum,
								 format_type_extended(fc_amprocForm->amproclefttype,
													  -1, FORMAT_TYPE_ALLOW_INVALID),
								 format_type_extended(fc_amprocForm->amprocrighttype,
													  -1, FORMAT_TYPE_ALLOW_INVALID),
								 fc_opfam.data,
								 format_procedure(fc_amprocForm->amproc));

				pfree(fc_opfam.data);

				systable_endscan(fc_amscan);
				table_close(fc_amprocDesc, AccessShareLock);
				break;
			}

		case OCLASS_REWRITE:
			{
				Relation	fc_ruleDesc;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_rcscan;
				HeapTuple	fc_tup;
				Form_pg_rewrite fc_rule;
				StringInfoData fc_rel;

				fc_ruleDesc = table_open(RewriteRelationId, AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_rewrite_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_rcscan = systable_beginscan(fc_ruleDesc, RewriteOidIndexId, true,
											NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_rcscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for rule %u",
							 fc_object->objectId);

					systable_endscan(fc_rcscan);
					table_close(fc_ruleDesc, AccessShareLock);
					break;
				}

				fc_rule = (Form_pg_rewrite) GETSTRUCT(fc_tup);

				initStringInfo(&fc_rel);
				fc_getRelationDescription(&fc_rel, fc_rule->ev_class, false);

				/* 翻译者：第二个%s是，例如，“表%s” */
				appendStringInfo(&fc_buffer, _("rule %s on %s"),
								 NameStr(fc_rule->rulename), fc_rel.data);
				pfree(fc_rel.data);
				systable_endscan(fc_rcscan);
				table_close(fc_ruleDesc, AccessShareLock);
				break;
			}

		case OCLASS_TRIGGER:
			{
				Relation	fc_trigDesc;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_tgscan;
				HeapTuple	fc_tup;
				Form_pg_trigger fc_trig;
				StringInfoData fc_rel;

				fc_trigDesc = table_open(TriggerRelationId, AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_trigger_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_tgscan = systable_beginscan(fc_trigDesc, TriggerOidIndexId, true,
											NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_tgscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for trigger %u",
							 fc_object->objectId);

					systable_endscan(fc_tgscan);
					table_close(fc_trigDesc, AccessShareLock);
					break;
				}

				fc_trig = (Form_pg_trigger) GETSTRUCT(fc_tup);

				initStringInfo(&fc_rel);
				fc_getRelationDescription(&fc_rel, fc_trig->tgrelid, false);

				/* 翻译者：第二个%s是，例如，“表%s” */
				appendStringInfo(&fc_buffer, _("trigger %s on %s"),
								 NameStr(fc_trig->tgname), fc_rel.data);
				pfree(fc_rel.data);
				systable_endscan(fc_tgscan);
				table_close(fc_trigDesc, AccessShareLock);
				break;
			}

		case OCLASS_SCHEMA:
			{
				char	   *fc_nspname;

				fc_nspname = get_namespace_name(fc_object->objectId);
				if (!fc_nspname)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for namespace %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfo(&fc_buffer, _("schema %s"), fc_nspname);
				break;
			}

		case OCLASS_STATISTIC_EXT:
			{
				HeapTuple	fc_stxTup;
				Form_pg_statistic_ext fc_stxForm;
				char	   *fc_nspname;

				fc_stxTup = SearchSysCache1(STATEXTOID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_stxTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for statistics object %u",
							 fc_object->objectId);
					break;
				}

				fc_stxForm = (Form_pg_statistic_ext) GETSTRUCT(fc_stxTup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (StatisticsObjIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_stxForm->stxnamespace);

				appendStringInfo(&fc_buffer, _("statistics object %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_stxForm->stxname)));

				ReleaseSysCache(fc_stxTup);
				break;
			}

		case OCLASS_TSPARSER:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_parser fc_prsForm;
				char	   *fc_nspname;

				fc_tup = SearchSysCache1(TSPARSEROID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search parser %u",
							 fc_object->objectId);
					break;
				}
				fc_prsForm = (Form_pg_ts_parser) GETSTRUCT(fc_tup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (TSParserIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_prsForm->prsnamespace);

				appendStringInfo(&fc_buffer, _("text search parser %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_prsForm->prsname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_TSDICT:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_dict fc_dictForm;
				char	   *fc_nspname;

				fc_tup = SearchSysCache1(TSDICTOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search dictionary %u",
							 fc_object->objectId);
					break;
				}

				fc_dictForm = (Form_pg_ts_dict) GETSTRUCT(fc_tup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (TSDictionaryIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_dictForm->dictnamespace);

				appendStringInfo(&fc_buffer, _("text search dictionary %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_dictForm->dictname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_TSTEMPLATE:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_template fc_tmplForm;
				char	   *fc_nspname;

				fc_tup = SearchSysCache1(TSTEMPLATEOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search template %u",
							 fc_object->objectId);
					break;
				}

				fc_tmplForm = (Form_pg_ts_template) GETSTRUCT(fc_tup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (TSTemplateIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_tmplForm->tmplnamespace);

				appendStringInfo(&fc_buffer, _("text search template %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_tmplForm->tmplname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_TSCONFIG:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_config fc_cfgForm;
				char	   *fc_nspname;

				fc_tup = SearchSysCache1(TSCONFIGOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search configuration %u",
							 fc_object->objectId);
					break;
				}

				fc_cfgForm = (Form_pg_ts_config) GETSTRUCT(fc_tup);

				/* 如果在搜索路径中不可见，则限定名称 */
				if (TSConfigIsVisible(fc_object->objectId))
					fc_nspname = NULL;
				else
					fc_nspname = get_namespace_name(fc_cfgForm->cfgnamespace);

				appendStringInfo(&fc_buffer, _("text search configuration %s"),
								 quote_qualified_identifier(fc_nspname,
															NameStr(fc_cfgForm->cfgname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_ROLE:
			{
				char	   *fc_username = GetUserNameFromId(fc_object->objectId,
														 fc_missing_ok);

				if (fc_username)
					appendStringInfo(&fc_buffer, _("role %s"), fc_username);
				break;
			}

		case OCLASS_DATABASE:
			{
				char	   *fc_datname;

				fc_datname = get_database_name(fc_object->objectId);
				if (!fc_datname)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for database %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfo(&fc_buffer, _("database %s"), fc_datname);
				break;
			}

		case OCLASS_TBLSPACE:
			{
				char	   *fc_tblspace;

				fc_tblspace = get_tablespace_name(fc_object->objectId);
				if (!fc_tblspace)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for tablespace %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfo(&fc_buffer, _("tablespace %s"), fc_tblspace);
				break;
			}

		case OCLASS_FDW:
			{
				ForeignDataWrapper *fc_fdw;

				fc_fdw = GetForeignDataWrapperExtended(fc_object->objectId,
													fc_missing_ok);
				if (fc_fdw)
					appendStringInfo(&fc_buffer, _("foreign-data wrapper %s"), fc_fdw->fdwname);
				break;
			}

		case OCLASS_FOREIGN_SERVER:
			{
				ForeignServer *fc_srv;

				fc_srv = GetForeignServerExtended(fc_object->objectId, fc_missing_ok);
				if (fc_srv)
					appendStringInfo(&fc_buffer, _("server %s"), fc_srv->servername);
				break;
			}

		case OCLASS_USER_MAPPING:
			{
				HeapTuple	fc_tup;
				Oid			fc_useid;
				char	   *fc_usename;
				Form_pg_user_mapping fc_umform;
				ForeignServer *fc_srv;

				fc_tup = SearchSysCache1(USERMAPPINGOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for user mapping %u",
							 fc_object->objectId);
					break;
				}

				fc_umform = (Form_pg_user_mapping) GETSTRUCT(fc_tup);
				fc_useid = fc_umform->umuser;
				fc_srv = GetForeignServer(fc_umform->umserver);

				ReleaseSysCache(fc_tup);

				if (OidIsValid(fc_useid))
					fc_usename = GetUserNameFromId(fc_useid, false);
				else
					fc_usename = "public";

				appendStringInfo(&fc_buffer, _("user mapping for %s on server %s"), fc_usename,
								 fc_srv->servername);
				break;
			}

		case OCLASS_DEFACL:
			{
				Relation	fc_defaclrel;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_rcscan;
				HeapTuple	fc_tup;
				Form_pg_default_acl fc_defacl;
				char	   *fc_rolename;
				char	   *fc_nspname;

				fc_defaclrel = table_open(DefaultAclRelationId, AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_default_acl_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_rcscan = systable_beginscan(fc_defaclrel, DefaultAclOidIndexId,
											true, NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_rcscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for default ACL %u",
							 fc_object->objectId);

					systable_endscan(fc_rcscan);
					table_close(fc_defaclrel, AccessShareLock);
					break;
				}

				fc_defacl = (Form_pg_default_acl) GETSTRUCT(fc_tup);

				fc_rolename = GetUserNameFromId(fc_defacl->defaclrole, false);

				if (OidIsValid(fc_defacl->defaclnamespace))
					fc_nspname = get_namespace_name(fc_defacl->defaclnamespace);
				else
					fc_nspname = NULL;

				switch (fc_defacl->defaclobjtype)
				{
					case DEFACLOBJ_RELATION:
						if (fc_nspname)
							appendStringInfo(&fc_buffer,
											 _("default privileges on new relations belonging to role %s in schema %s"),
											 fc_rolename, fc_nspname);
						else
							appendStringInfo(&fc_buffer,
											 _("default privileges on new relations belonging to role %s"),
											 fc_rolename);
						break;
					case DEFACLOBJ_SEQUENCE:
						if (fc_nspname)
							appendStringInfo(&fc_buffer,
											 _("default privileges on new sequences belonging to role %s in schema %s"),
											 fc_rolename, fc_nspname);
						else
							appendStringInfo(&fc_buffer,
											 _("default privileges on new sequences belonging to role %s"),
											 fc_rolename);
						break;
					case DEFACLOBJ_FUNCTION:
						if (fc_nspname)
							appendStringInfo(&fc_buffer,
											 _("default privileges on new functions belonging to role %s in schema %s"),
											 fc_rolename, fc_nspname);
						else
							appendStringInfo(&fc_buffer,
											 _("default privileges on new functions belonging to role %s"),
											 fc_rolename);
						break;
					case DEFACLOBJ_TYPE:
						if (fc_nspname)
							appendStringInfo(&fc_buffer,
											 _("default privileges on new types belonging to role %s in schema %s"),
											 fc_rolename, fc_nspname);
						else
							appendStringInfo(&fc_buffer,
											 _("default privileges on new types belonging to role %s"),
											 fc_rolename);
						break;
					case DEFACLOBJ_NAMESPACE:
						Assert(!fc_nspname);
						appendStringInfo(&fc_buffer,
										 _("default privileges on new schemas belonging to role %s"),
										 fc_rolename);
						break;
					default:
						/* 不应该到这里 */
						if (fc_nspname)
							appendStringInfo(&fc_buffer,
											 _("default privileges belonging to role %s in schema %s"),
											 fc_rolename, fc_nspname);
						else
							appendStringInfo(&fc_buffer,
											 _("default privileges belonging to role %s"),
											 fc_rolename);
						break;
				}

				systable_endscan(fc_rcscan);
				table_close(fc_defaclrel, AccessShareLock);
				break;
			}

		case OCLASS_EXTENSION:
			{
				char	   *fc_extname;

				fc_extname = get_extension_name(fc_object->objectId);
				if (!fc_extname)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for extension %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfo(&fc_buffer, _("extension %s"), fc_extname);
				break;
			}

		case OCLASS_EVENT_TRIGGER:
			{
				HeapTuple	fc_tup;

				fc_tup = SearchSysCache1(EVENTTRIGGEROID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for event trigger %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfo(&fc_buffer, _("event trigger %s"),
								 NameStr(((Form_pg_event_trigger) GETSTRUCT(fc_tup))->evtname));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_PARAMETER_ACL:
			{
				HeapTuple	fc_tup;
				Datum		fc_nameDatum;
				bool		fc_isNull;
				char	   *fc_parname;

				fc_tup = SearchSysCache1(PARAMETERACLOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for parameter ACL %u",
							 fc_object->objectId);
					break;
				}
				fc_nameDatum = SysCacheGetAttr(PARAMETERACLOID, fc_tup,
											Anum_pg_parameter_acl_parname,
											&fc_isNull);
				Assert(!fc_isNull);
				fc_parname = TextDatumGetCString(fc_nameDatum);
				appendStringInfo(&fc_buffer, _("parameter %s"), fc_parname);
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_POLICY:
			{
				Relation	fc_policy_rel;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_sscan;
				HeapTuple	fc_tuple;
				Form_pg_policy fc_form_policy;
				StringInfoData fc_rel;

				fc_policy_rel = table_open(PolicyRelationId, AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_policy_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_sscan = systable_beginscan(fc_policy_rel, PolicyOidIndexId,
										   true, NULL, 1, fc_skey);

				fc_tuple = systable_getnext(fc_sscan);

				if (!HeapTupleIsValid(fc_tuple))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for policy %u",
							 fc_object->objectId);

					systable_endscan(fc_sscan);
					table_close(fc_policy_rel, AccessShareLock);
					break;
				}

				fc_form_policy = (Form_pg_policy) GETSTRUCT(fc_tuple);

				initStringInfo(&fc_rel);
				fc_getRelationDescription(&fc_rel, fc_form_policy->polrelid, false);

				/* 翻译者：第二个%s是，例如，“表%s” */
				appendStringInfo(&fc_buffer, _("policy %s on %s"),
								 NameStr(fc_form_policy->polname), fc_rel.data);
				pfree(fc_rel.data);
				systable_endscan(fc_sscan);
				table_close(fc_policy_rel, AccessShareLock);
				break;
			}

		case OCLASS_PUBLICATION:
			{
				char	   *fc_pubname = get_publication_name(fc_object->objectId,
														   fc_missing_ok);

				if (fc_pubname)
					appendStringInfo(&fc_buffer, _("publication %s"), fc_pubname);
				break;
			}

		case OCLASS_PUBLICATION_NAMESPACE:
			{
				char	   *fc_pubname;
				char	   *fc_nspname;

				if (!fc_getPublicationSchemaInfo(fc_object, fc_missing_ok,
											  &fc_pubname, &fc_nspname))
					break;

				appendStringInfo(&fc_buffer, _("publication of schema %s in publication %s"),
								 fc_nspname, fc_pubname);
				pfree(fc_pubname);
				pfree(fc_nspname);
				break;
			}

		case OCLASS_PUBLICATION_REL:
			{
				HeapTuple	fc_tup;
				char	   *fc_pubname;
				Form_pg_publication_rel fc_prform;
				StringInfoData fc_rel;

				fc_tup = SearchSysCache1(PUBLICATIONREL,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for publication table %u",
							 fc_object->objectId);
					break;
				}

				fc_prform = (Form_pg_publication_rel) GETSTRUCT(fc_tup);
				fc_pubname = get_publication_name(fc_prform->prpubid, false);

				initStringInfo(&fc_rel);
				fc_getRelationDescription(&fc_rel, fc_prform->prrelid, false);

				/* 翻译者：第一个%s是，例如，“表%s” */
				appendStringInfo(&fc_buffer, _("publication of %s in publication %s"),
								 fc_rel.data, fc_pubname);
				pfree(fc_rel.data);
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_SUBSCRIPTION:
			{
				char	   *fc_subname = get_subscription_name(fc_object->objectId,
															fc_missing_ok);

				if (fc_subname)
					appendStringInfo(&fc_buffer, _("subscription %s"), fc_subname);
				break;
			}

		case OCLASS_TRANSFORM:
			{
				HeapTuple	fc_trfTup;
				Form_pg_transform fc_trfForm;

				fc_trfTup = SearchSysCache1(TRFOID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_trfTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for transform %u",
							 fc_object->objectId);
					break;
				}

				fc_trfForm = (Form_pg_transform) GETSTRUCT(fc_trfTup);

				appendStringInfo(&fc_buffer, _("transform for %s language %s"),
								 format_type_be(fc_trfForm->trftype),
								 get_language_name(fc_trfForm->trflang, false));

				ReleaseSysCache(fc_trfTup);
				break;
			}

			/*
			 * 这里故意没有默认 case；我们希望编译器
			 * 在上面未处理新 OCLASS 时发出警告。
			 */
	}

	/* 空缓冲区相当于未找到对象 */
	if (fc_buffer.len == 0)
		return NULL;

	return fc_buffer.data;
}

/*
 * getObjectDescriptionOids：同上，除了对象是通过Oids指定的
 */
char * getObjectDescriptionOids(Oid fc_classid, Oid fc_objid)
{
	ObjectAddress fc_address;

	fc_address.classId = fc_classid;
	fc_address.objectId = fc_objid;
	fc_address.objectSubId = 0;

	return getObjectDescription(&fc_address, false);
}

/*
 * getObjectDescription的子例程：描述一个关系
 *
 * 结果附加到“缓冲区”。
 */
static void fc_getRelationDescription(StringInfo fc_buffer, Oid fc_relid, bool fc_missing_ok)
{
	HeapTuple	fc_relTup;
	Form_pg_class fc_relForm;
	char	   *fc_nspname;
	char	   *fc_relname;

	fc_relTup = SearchSysCache1(RELOID,
							 ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_relTup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for relation %u", fc_relid);
		return;
	}
	fc_relForm = (Form_pg_class) GETSTRUCT(fc_relTup);

	/* 如果在搜索路径中不可见，则限定名称 */
	if (RelationIsVisible(fc_relid))
		fc_nspname = NULL;
	else
		fc_nspname = get_namespace_name(fc_relForm->relnamespace);

	fc_relname = quote_qualified_identifier(fc_nspname, NameStr(fc_relForm->relname));

	switch (fc_relForm->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			appendStringInfo(fc_buffer, _("table %s"),
							 fc_relname);
			break;
		case RELKIND_INDEX:
		case RELKIND_PARTITIONED_INDEX:
			appendStringInfo(fc_buffer, _("index %s"),
							 fc_relname);
			break;
		case RELKIND_SEQUENCE:
			appendStringInfo(fc_buffer, _("sequence %s"),
							 fc_relname);
			break;
		case RELKIND_TOASTVALUE:
			appendStringInfo(fc_buffer, _("toast table %s"),
							 fc_relname);
			break;
		case RELKIND_VIEW:
			appendStringInfo(fc_buffer, _("view %s"),
							 fc_relname);
			break;
		case RELKIND_MATVIEW:
			appendStringInfo(fc_buffer, _("materialized view %s"),
							 fc_relname);
			break;
		case RELKIND_COMPOSITE_TYPE:
			appendStringInfo(fc_buffer, _("composite type %s"),
							 fc_relname);
			break;
		case RELKIND_FOREIGN_TABLE:
			appendStringInfo(fc_buffer, _("foreign table %s"),
							 fc_relname);
			break;
		default:
			/* 不应该到这里 */
			appendStringInfo(fc_buffer, _("relation %s"),
							 fc_relname);
			break;
	}

	ReleaseSysCache(fc_relTup);
}

/*
 * getObjectDescription的子例程：描述一个操作符族
 */
static void fc_getOpFamilyDescription(StringInfo fc_buffer, Oid fc_opfid, bool fc_missing_ok)
{
	HeapTuple	fc_opfTup;
	Form_pg_opfamily fc_opfForm;
	HeapTuple	fc_amTup;
	Form_pg_am	fc_amForm;
	char	   *fc_nspname;

	fc_opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(fc_opfid));
	if (!HeapTupleIsValid(fc_opfTup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for opfamily %u", fc_opfid);
		return;
	}
	fc_opfForm = (Form_pg_opfamily) GETSTRUCT(fc_opfTup);

	fc_amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(fc_opfForm->opfmethod));
	if (!HeapTupleIsValid(fc_amTup))
		elog(ERROR, "cache lookup failed for access method %u",
			 fc_opfForm->opfmethod);
	fc_amForm = (Form_pg_am) GETSTRUCT(fc_amTup);

	/* 如果在搜索路径中不可见，则限定名称 */
	if (OpfamilyIsVisible(fc_opfid))
		fc_nspname = NULL;
	else
		fc_nspname = get_namespace_name(fc_opfForm->opfnamespace);

	appendStringInfo(fc_buffer, _("operator family %s for access method %s"),
					 quote_qualified_identifier(fc_nspname,
												NameStr(fc_opfForm->opfname)),
					 NameStr(fc_amForm->amname));

	ReleaseSysCache(fc_amTup);
	ReleaseSysCache(fc_opfTup);
}

/*
 * SQL级可调用版本的getObjectDescription
 */
Datum pg_describe_object(PG_FUNCTION_ARGS)
{
	Oid			fc_classid = PG_GETARG_OID(0);
	Oid			fc_objid = PG_GETARG_OID(1);
	int32		fc_objsubid = PG_GETARG_INT32(2);
	char	   *fc_description;
	ObjectAddress fc_address;

	/* 对于pg_depend中的“固定”项目，返回null */
	if (!OidIsValid(fc_classid) && !OidIsValid(fc_objid))
		PG_RETURN_NULL();

	fc_address.classId = fc_classid;
	fc_address.objectId = fc_objid;
	fc_address.objectSubId = fc_objsubid;

	fc_description = getObjectDescription(&fc_address, true);

	if (fc_description == NULL)
		PG_RETURN_NULL();

	PG_RETURN_TEXT_P(cstring_to_text(fc_description));
}

/*
 * SQL级可调用函数以获取对象类型和标识
 */
Datum pg_identify_object(PG_FUNCTION_ARGS)
{
	Oid			fc_classid = PG_GETARG_OID(0);
	Oid			fc_objid = PG_GETARG_OID(1);
	int32		fc_objsubid = PG_GETARG_INT32(2);
	Oid			fc_schema_oid = InvalidOid;
	const char *fc_objname = NULL;
	char	   *fc_objidentity;
	ObjectAddress fc_address;
	Datum		fc_values[4];
	bool		fc_nulls[4];
	TupleDesc	fc_tupdesc;
	HeapTuple	fc_htup;

	fc_address.classId = fc_classid;
	fc_address.objectId = fc_objid;
	fc_address.objectSubId = fc_objsubid;

	/*
	 * 为结果行构造一个元组描述符。必须与此函数的 pg_proc 条目匹配！
	 */
	fc_tupdesc = CreateTemplateTupleDesc(4);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "type",
					   TEXTOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "schema",
					   TEXTOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "name",
					   TEXTOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 4, "identity",
					   TEXTOID, -1, 0);

	fc_tupdesc = BlessTupleDesc(fc_tupdesc);

	if (is_objectclass_supported(fc_address.classId))
	{
		HeapTuple	fc_objtup;
		Relation	fc_catalog = table_open(fc_address.classId, AccessShareLock);

		fc_objtup = get_catalog_object_by_oid(fc_catalog,
										   get_object_attnum_oid(fc_address.classId),
										   fc_address.objectId);
		if (fc_objtup != NULL)
		{
			bool		fc_isnull;
			AttrNumber	fc_nspAttnum;
			AttrNumber	fc_nameAttnum;

			fc_nspAttnum = get_object_attnum_namespace(fc_address.classId);
			if (fc_nspAttnum != InvalidAttrNumber)
			{
				fc_schema_oid = heap_getattr(fc_objtup, fc_nspAttnum,
										  RelationGetDescr(fc_catalog), &fc_isnull);
				if (fc_isnull)
					elog(ERROR, "invalid null namespace in object %u/%u/%d",
						 fc_address.classId, fc_address.objectId, fc_address.objectSubId);
			}

			/*
			 * 只有在对象名称可以被使用（与模式名称
			 * 一起，如果有）作为唯一标识符时，我们才返回对象名称。
			 */
			if (get_object_namensp_unique(fc_address.classId))
			{
				fc_nameAttnum = get_object_attnum_name(fc_address.classId);
				if (fc_nameAttnum != InvalidAttrNumber)
				{
					Datum		fc_nameDatum;

					fc_nameDatum = heap_getattr(fc_objtup, fc_nameAttnum,
											 RelationGetDescr(fc_catalog), &fc_isnull);
					if (fc_isnull)
						elog(ERROR, "invalid null name in object %u/%u/%d",
							 fc_address.classId, fc_address.objectId, fc_address.objectSubId);
					fc_objname = quote_identifier(NameStr(*(DatumGetName(fc_nameDatum))));
				}
			}
		}

		table_close(fc_catalog, AccessShareLock);
	}

	/* 对象类型，永远不可能为NULL */
	fc_values[0] = CStringGetTextDatum(getObjectTypeDescription(&fc_address, true));
	fc_nulls[0] = false;

	/*
	 * 在执行任何操作之前，提取对象标识。如果找不到标识，
	 * 将对象类型以外的所有字段设置为NULL。
	 */
	fc_objidentity = getObjectIdentity(&fc_address, true);

	/* 模式名称 */
	if (OidIsValid(fc_schema_oid) && fc_objidentity)
	{
		const char *fc_schema = quote_identifier(get_namespace_name(fc_schema_oid));

		fc_values[1] = CStringGetTextDatum(fc_schema);
		fc_nulls[1] = false;
	}
	else
		fc_nulls[1] = true;

	/* 对象名称 */
	if (fc_objname && fc_objidentity)
	{
		fc_values[2] = CStringGetTextDatum(fc_objname);
		fc_nulls[2] = false;
	}
	else
		fc_nulls[2] = true;

	/* 对象标识 */
	if (fc_objidentity)
	{
		fc_values[3] = CStringGetTextDatum(fc_objidentity);
		fc_nulls[3] = false;
	}
	else
		fc_nulls[3] = true;

	fc_htup = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	PG_RETURN_DATUM(HeapTupleGetDatum(fc_htup));
}

/*
 * SQL级可调用函数以获取对象类型和标识
 */
Datum pg_identify_object_as_address(PG_FUNCTION_ARGS)
{
	Oid			fc_classid = PG_GETARG_OID(0);
	Oid			fc_objid = PG_GETARG_OID(1);
	int32		fc_objsubid = PG_GETARG_INT32(2);
	ObjectAddress fc_address;
	char	   *fc_identity;
	List	   *fc_names;
	List	   *fc_args;
	Datum		fc_values[3];
	bool		fc_nulls[3];
	TupleDesc	fc_tupdesc;
	HeapTuple	fc_htup;

	fc_address.classId = fc_classid;
	fc_address.objectId = fc_objid;
	fc_address.objectSubId = fc_objsubid;

	/*
	 * 为结果行构造一个元组描述符。必须与此函数的 pg_proc 条目匹配！
	 */
	fc_tupdesc = CreateTemplateTupleDesc(3);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 1, "type",
					   TEXTOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 2, "object_names",
					   TEXTARRAYOID, -1, 0);
	TupleDescInitEntry(fc_tupdesc, (AttrNumber) 3, "object_args",
					   TEXTARRAYOID, -1, 0);

	fc_tupdesc = BlessTupleDesc(fc_tupdesc);

	/* 对象类型，永远不可能为NULL */
	fc_values[0] = CStringGetTextDatum(getObjectTypeDescription(&fc_address, true));
	fc_nulls[0] = false;

	/* 对象标识 */
	fc_identity = getObjectIdentityParts(&fc_address, &fc_names, &fc_args, true);
	if (fc_identity == NULL)
	{
		fc_nulls[1] = true;
		fc_nulls[2] = true;
	}
	else
	{
		pfree(fc_identity);

		/* object_names */
		if (fc_names != NIL)
			fc_values[1] = PointerGetDatum(strlist_to_textarray(fc_names));
		else
			fc_values[1] = PointerGetDatum(construct_empty_array(TEXTOID));
		fc_nulls[1] = false;

		/* object_args */
		if (fc_args)
			fc_values[2] = PointerGetDatum(strlist_to_textarray(fc_args));
		else
			fc_values[2] = PointerGetDatum(construct_empty_array(TEXTOID));
		fc_nulls[2] = false;
	}

	fc_htup = heap_form_tuple(fc_tupdesc, fc_values, fc_nulls);

	PG_RETURN_DATUM(HeapTupleGetDatum(fc_htup));
}

/*
 * 返回一个palloc分配的字符串，描述传递地址对应的对象类型。
 *
 * 请保持ObjectTypeMap与此同步。
 */
char * getObjectTypeDescription(const ObjectAddress *fc_object, bool fc_missing_ok)
{
	StringInfoData fc_buffer;

	initStringInfo(&fc_buffer);

	switch (getObjectClass(fc_object))
	{
		case OCLASS_CLASS:
			fc_getRelationTypeDescription(&fc_buffer, fc_object->objectId,
									   fc_object->objectSubId,
									   fc_missing_ok);
			break;

		case OCLASS_PROC:
			fc_getProcedureTypeDescription(&fc_buffer, fc_object->objectId,
										fc_missing_ok);
			break;

		case OCLASS_TYPE:
			appendStringInfoString(&fc_buffer, "type");
			break;

		case OCLASS_CAST:
			appendStringInfoString(&fc_buffer, "cast");
			break;

		case OCLASS_COLLATION:
			appendStringInfoString(&fc_buffer, "collation");
			break;

		case OCLASS_CONSTRAINT:
			fc_getConstraintTypeDescription(&fc_buffer, fc_object->objectId,
										 fc_missing_ok);
			break;

		case OCLASS_CONVERSION:
			appendStringInfoString(&fc_buffer, "conversion");
			break;

		case OCLASS_DEFAULT:
			appendStringInfoString(&fc_buffer, "default value");
			break;

		case OCLASS_LANGUAGE:
			appendStringInfoString(&fc_buffer, "language");
			break;

		case OCLASS_LARGEOBJECT:
			appendStringInfoString(&fc_buffer, "large object");
			break;

		case OCLASS_OPERATOR:
			appendStringInfoString(&fc_buffer, "operator");
			break;

		case OCLASS_OPCLASS:
			appendStringInfoString(&fc_buffer, "operator class");
			break;

		case OCLASS_OPFAMILY:
			appendStringInfoString(&fc_buffer, "operator family");
			break;

		case OCLASS_AM:
			appendStringInfoString(&fc_buffer, "access method");
			break;

		case OCLASS_AMOP:
			appendStringInfoString(&fc_buffer, "operator of access method");
			break;

		case OCLASS_AMPROC:
			appendStringInfoString(&fc_buffer, "function of access method");
			break;

		case OCLASS_REWRITE:
			appendStringInfoString(&fc_buffer, "rule");
			break;

		case OCLASS_TRIGGER:
			appendStringInfoString(&fc_buffer, "trigger");
			break;

		case OCLASS_SCHEMA:
			appendStringInfoString(&fc_buffer, "schema");
			break;

		case OCLASS_STATISTIC_EXT:
			appendStringInfoString(&fc_buffer, "statistics object");
			break;

		case OCLASS_TSPARSER:
			appendStringInfoString(&fc_buffer, "text search parser");
			break;

		case OCLASS_TSDICT:
			appendStringInfoString(&fc_buffer, "text search dictionary");
			break;

		case OCLASS_TSTEMPLATE:
			appendStringInfoString(&fc_buffer, "text search template");
			break;

		case OCLASS_TSCONFIG:
			appendStringInfoString(&fc_buffer, "text search configuration");
			break;

		case OCLASS_ROLE:
			appendStringInfoString(&fc_buffer, "role");
			break;

		case OCLASS_DATABASE:
			appendStringInfoString(&fc_buffer, "database");
			break;

		case OCLASS_TBLSPACE:
			appendStringInfoString(&fc_buffer, "tablespace");
			break;

		case OCLASS_FDW:
			appendStringInfoString(&fc_buffer, "foreign-data wrapper");
			break;

		case OCLASS_FOREIGN_SERVER:
			appendStringInfoString(&fc_buffer, "server");
			break;

		case OCLASS_USER_MAPPING:
			appendStringInfoString(&fc_buffer, "user mapping");
			break;

		case OCLASS_DEFACL:
			appendStringInfoString(&fc_buffer, "default acl");
			break;

		case OCLASS_EXTENSION:
			appendStringInfoString(&fc_buffer, "extension");
			break;

		case OCLASS_EVENT_TRIGGER:
			appendStringInfoString(&fc_buffer, "event trigger");
			break;

		case OCLASS_PARAMETER_ACL:
			appendStringInfoString(&fc_buffer, "parameter ACL");
			break;

		case OCLASS_POLICY:
			appendStringInfoString(&fc_buffer, "policy");
			break;

		case OCLASS_PUBLICATION:
			appendStringInfoString(&fc_buffer, "publication");
			break;

		case OCLASS_PUBLICATION_NAMESPACE:
			appendStringInfoString(&fc_buffer, "publication namespace");
			break;

		case OCLASS_PUBLICATION_REL:
			appendStringInfoString(&fc_buffer, "publication relation");
			break;

		case OCLASS_SUBSCRIPTION:
			appendStringInfoString(&fc_buffer, "subscription");
			break;

		case OCLASS_TRANSFORM:
			appendStringInfoString(&fc_buffer, "transform");
			break;

			/*
			 * 这里故意没有默认 case；我们希望编译器
			 * 在上面未处理新 OCLASS 时发出警告。
			 */
	}

	/* 结果绝不会为空 */
	Assert(fc_buffer.len > 0);

	return fc_buffer.data;
}

/*
 * getObjectTypeDescription的子例程：描述一种关系类型
 */
static void fc_getRelationTypeDescription(StringInfo fc_buffer, Oid fc_relid, int32 fc_objectSubId,
						   bool fc_missing_ok)
{
	HeapTuple	fc_relTup;
	Form_pg_class fc_relForm;

	fc_relTup = SearchSysCache1(RELOID,
							 ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_relTup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for relation %u", fc_relid);

		/* 对于未定义对象回退到“关系” */
		appendStringInfoString(fc_buffer, "relation");
		return;
	}
	fc_relForm = (Form_pg_class) GETSTRUCT(fc_relTup);

	switch (fc_relForm->relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			appendStringInfoString(fc_buffer, "table");
			break;
		case RELKIND_INDEX:
		case RELKIND_PARTITIONED_INDEX:
			appendStringInfoString(fc_buffer, "index");
			break;
		case RELKIND_SEQUENCE:
			appendStringInfoString(fc_buffer, "sequence");
			break;
		case RELKIND_TOASTVALUE:
			appendStringInfoString(fc_buffer, "toast table");
			break;
		case RELKIND_VIEW:
			appendStringInfoString(fc_buffer, "view");
			break;
		case RELKIND_MATVIEW:
			appendStringInfoString(fc_buffer, "materialized view");
			break;
		case RELKIND_COMPOSITE_TYPE:
			appendStringInfoString(fc_buffer, "composite type");
			break;
		case RELKIND_FOREIGN_TABLE:
			appendStringInfoString(fc_buffer, "foreign table");
			break;
		default:
			/* 不应该到这里 */
			appendStringInfoString(fc_buffer, "relation");
			break;
	}

	if (fc_objectSubId != 0)
		appendStringInfoString(fc_buffer, " column");

	ReleaseSysCache(fc_relTup);
}

/*
 * getObjectTypeDescription的子例程：描述一种约束类型
 */
static void fc_getConstraintTypeDescription(StringInfo fc_buffer, Oid fc_constroid, bool fc_missing_ok)
{
	Relation	fc_constrRel;
	HeapTuple	fc_constrTup;
	Form_pg_constraint fc_constrForm;

	fc_constrRel = table_open(ConstraintRelationId, AccessShareLock);
	fc_constrTup = get_catalog_object_by_oid(fc_constrRel, Anum_pg_constraint_oid,
										  fc_constroid);
	if (!HeapTupleIsValid(fc_constrTup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for constraint %u", fc_constroid);

		table_close(fc_constrRel, AccessShareLock);

		/* 回退到“约束”以处理未定义的对象 */
		appendStringInfoString(fc_buffer, "constraint");
		return;
	}

	fc_constrForm = (Form_pg_constraint) GETSTRUCT(fc_constrTup);

	if (OidIsValid(fc_constrForm->conrelid))
		appendStringInfoString(fc_buffer, "table constraint");
	else if (OidIsValid(fc_constrForm->contypid))
		appendStringInfoString(fc_buffer, "domain constraint");
	else
		elog(ERROR, "invalid constraint %u", fc_constrForm->oid);

	table_close(fc_constrRel, AccessShareLock);
}

/*
 * getObjectTypeDescription的子例程：描述过程类型
 */
static void fc_getProcedureTypeDescription(StringInfo fc_buffer, Oid fc_procid,
							bool fc_missing_ok)
{
	HeapTuple	fc_procTup;
	Form_pg_proc fc_procForm;

	fc_procTup = SearchSysCache1(PROCOID,
							  ObjectIdGetDatum(fc_procid));
	if (!HeapTupleIsValid(fc_procTup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for procedure %u", fc_procid);

		/* 默认回退为"procedure"以应对未定义的对象 */
		appendStringInfoString(fc_buffer, "routine");
		return;
	}
	fc_procForm = (Form_pg_proc) GETSTRUCT(fc_procTup);

	if (fc_procForm->prokind == PROKIND_AGGREGATE)
		appendStringInfoString(fc_buffer, "aggregate");
	else if (fc_procForm->prokind == PROKIND_PROCEDURE)
		appendStringInfoString(fc_buffer, "procedure");
	else						/* 函数或窗口函数 */
		appendStringInfoString(fc_buffer, "function");

	ReleaseSysCache(fc_procTup);
}

/*
 * 获取给定对象的标识，作为一个palloc的字符串。
 *
 * 这是为机器消费而设计的，因此没有翻译。所有元素在适当时都是
 * 模式限定的。如果无法找到该对象，则返回NULL。
 */
char * getObjectIdentity(const ObjectAddress *fc_object, bool fc_missing_ok)
{
	return getObjectIdentityParts(fc_object, NULL, NULL, fc_missing_ok);
}

/*
 * 如上所述，但更详细。
 *
 * 返回值有两组：标识本身作为一个palloc的字符串返回。
 * objname和objargs，如果不为NULL，是输出参数，
 * 接收有助于返回给get_object_address()以重构ObjectAddress的C-字符串列表。
 * 如果无法找到该对象，则返回NULL。
 */
char * getObjectIdentityParts(const ObjectAddress *fc_object,
					   List **fc_objname, List **fc_objargs,
					   bool fc_missing_ok)
{
	StringInfoData fc_buffer;

	initStringInfo(&fc_buffer);

	/*
	 * 确保同时传递了objname和objargs，或都未传递；并将它们初始化为
	 * 空列表。对于objname，这在所有情况下都是无用的，因为它将在
	 * switch内部初始化；但我们还是这样做，以便下文测试没有分支
	 * 将其留未设置。
	 */
	Assert(PointerIsValid(fc_objname) == PointerIsValid(fc_objargs));
	if (fc_objname)
	{
		*fc_objname = NIL;
		*fc_objargs = NIL;
	}

	switch (getObjectClass(fc_object))
	{
		case OCLASS_CLASS:
			{
				char	   *fc_attr = NULL;

				/*
				 * 首先检查属性，以便如果缺失，我们可以跳过整个
				 * 关系描述。
				 */
				if (fc_object->objectSubId != 0)
				{
					fc_attr = get_attname(fc_object->objectId,
									   fc_object->objectSubId,
									   fc_missing_ok);

					if (fc_missing_ok && fc_attr == NULL)
						break;
				}

				fc_getRelationIdentity(&fc_buffer, fc_object->objectId, fc_objname,
									fc_missing_ok);
				if (fc_objname && *fc_objname == NIL)
					break;

				if (fc_attr)
				{
					appendStringInfo(&fc_buffer, ".%s",
									 quote_identifier(fc_attr));
					if (fc_objname)
						*fc_objname = lappend(*fc_objname, fc_attr);
				}
			}
			break;

		case OCLASS_PROC:
			{
				bits16		fc_flags = FORMAT_PROC_FORCE_QUALIFY | FORMAT_PROC_INVALID_AS_NULL;
				char	   *fc_proname = format_procedure_extended(fc_object->objectId,
																fc_flags);

				if (fc_proname == NULL)
					break;

				appendStringInfoString(&fc_buffer, fc_proname);
				if (fc_objname)
					format_procedure_parts(fc_object->objectId, fc_objname, fc_objargs,
										   fc_missing_ok);
				break;
			}

		case OCLASS_TYPE:
			{
				bits16		fc_flags = FORMAT_TYPE_INVALID_AS_NULL | FORMAT_TYPE_FORCE_QUALIFY;
				char	   *fc_typeout;

				fc_typeout = format_type_extended(fc_object->objectId, -1, fc_flags);

				if (fc_typeout == NULL)
					break;

				appendStringInfoString(&fc_buffer, fc_typeout);
				if (fc_objname)
					*fc_objname = list_make1(fc_typeout);
			}
			break;

		case OCLASS_CAST:
			{
				Relation	fc_castRel;
				HeapTuple	fc_tup;
				Form_pg_cast fc_castForm;

				fc_castRel = table_open(CastRelationId, AccessShareLock);

				fc_tup = get_catalog_object_by_oid(fc_castRel, Anum_pg_cast_oid,
												fc_object->objectId);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for cast %u",
							 fc_object->objectId);

					table_close(fc_castRel, AccessShareLock);
					break;
				}

				fc_castForm = (Form_pg_cast) GETSTRUCT(fc_tup);

				appendStringInfo(&fc_buffer, "(%s AS %s)",
								 format_type_be_qualified(fc_castForm->castsource),
								 format_type_be_qualified(fc_castForm->casttarget));

				if (fc_objname)
				{
					*fc_objname = list_make1(format_type_be_qualified(fc_castForm->castsource));
					*fc_objargs = list_make1(format_type_be_qualified(fc_castForm->casttarget));
				}

				table_close(fc_castRel, AccessShareLock);
				break;
			}

		case OCLASS_COLLATION:
			{
				HeapTuple	fc_collTup;
				Form_pg_collation fc_coll;
				char	   *fc_schema;

				fc_collTup = SearchSysCache1(COLLOID,
										  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_collTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for collation %u",
							 fc_object->objectId);
					break;
				}
				fc_coll = (Form_pg_collation) GETSTRUCT(fc_collTup);
				fc_schema = get_namespace_name_or_temp(fc_coll->collnamespace);
				appendStringInfoString(&fc_buffer,
									   quote_qualified_identifier(fc_schema,
																  NameStr(fc_coll->collname)));
				if (fc_objname)
					*fc_objname = list_make2(fc_schema,
										  pstrdup(NameStr(fc_coll->collname)));
				ReleaseSysCache(fc_collTup);
				break;
			}

		case OCLASS_CONSTRAINT:
			{
				HeapTuple	fc_conTup;
				Form_pg_constraint fc_con;

				fc_conTup = SearchSysCache1(CONSTROID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_conTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for constraint %u",
							 fc_object->objectId);
					break;
				}
				fc_con = (Form_pg_constraint) GETSTRUCT(fc_conTup);

				if (OidIsValid(fc_con->conrelid))
				{
					appendStringInfo(&fc_buffer, "%s on ",
									 quote_identifier(NameStr(fc_con->conname)));
					fc_getRelationIdentity(&fc_buffer, fc_con->conrelid, fc_objname,
										false);
					if (fc_objname)
						*fc_objname = lappend(*fc_objname, pstrdup(NameStr(fc_con->conname)));
				}
				else
				{
					ObjectAddress fc_domain;

					Assert(OidIsValid(fc_con->contypid));
					fc_domain.classId = TypeRelationId;
					fc_domain.objectId = fc_con->contypid;
					fc_domain.objectSubId = 0;

					appendStringInfo(&fc_buffer, "%s on %s",
									 quote_identifier(NameStr(fc_con->conname)),
									 getObjectIdentityParts(&fc_domain, fc_objname,
															fc_objargs, false));

					if (fc_objname)
						*fc_objargs = lappend(*fc_objargs, pstrdup(NameStr(fc_con->conname)));
				}

				ReleaseSysCache(fc_conTup);
				break;
			}

		case OCLASS_CONVERSION:
			{
				HeapTuple	fc_conTup;
				Form_pg_conversion fc_conForm;
				char	   *fc_schema;

				fc_conTup = SearchSysCache1(CONVOID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_conTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for conversion %u",
							 fc_object->objectId);
					break;
				}
				fc_conForm = (Form_pg_conversion) GETSTRUCT(fc_conTup);
				fc_schema = get_namespace_name_or_temp(fc_conForm->connamespace);
				appendStringInfoString(&fc_buffer,
									   quote_qualified_identifier(fc_schema,
																  NameStr(fc_conForm->conname)));
				if (fc_objname)
					*fc_objname = list_make2(fc_schema,
										  pstrdup(NameStr(fc_conForm->conname)));
				ReleaseSysCache(fc_conTup);
				break;
			}

		case OCLASS_DEFAULT:
			{
				ObjectAddress fc_colobject;

				fc_colobject = GetAttrDefaultColumnAddress(fc_object->objectId);

				if (!OidIsValid(fc_colobject.objectId))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for attrdef %u",
							 fc_object->objectId);
					break;
				}

				appendStringInfo(&fc_buffer, "for %s",
								 getObjectIdentityParts(&fc_colobject,
														fc_objname, fc_objargs,
														false));
				break;
			}

		case OCLASS_LANGUAGE:
			{
				HeapTuple	fc_langTup;
				Form_pg_language fc_langForm;

				fc_langTup = SearchSysCache1(LANGOID,
										  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_langTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for language %u",
							 fc_object->objectId);
					break;
				}
				fc_langForm = (Form_pg_language) GETSTRUCT(fc_langTup);
				appendStringInfoString(&fc_buffer,
									   quote_identifier(NameStr(fc_langForm->lanname)));
				if (fc_objname)
					*fc_objname = list_make1(pstrdup(NameStr(fc_langForm->lanname)));
				ReleaseSysCache(fc_langTup);
				break;
			}
		case OCLASS_LARGEOBJECT:
			if (!LargeObjectExists(fc_object->objectId))
				break;
			appendStringInfo(&fc_buffer, "%u",
							 fc_object->objectId);
			if (fc_objname)
				*fc_objname = list_make1(psprintf("%u", fc_object->objectId));
			break;

		case OCLASS_OPERATOR:
			{
				bits16		fc_flags = FORMAT_OPERATOR_FORCE_QUALIFY | FORMAT_OPERATOR_INVALID_AS_NULL;
				char	   *fc_oprname = format_operator_extended(fc_object->objectId,
															   fc_flags);

				if (fc_oprname == NULL)
					break;

				appendStringInfoString(&fc_buffer, fc_oprname);
				if (fc_objname)
					format_operator_parts(fc_object->objectId, fc_objname, fc_objargs, fc_missing_ok);
				break;
			}

		case OCLASS_OPCLASS:
			{
				HeapTuple	fc_opcTup;
				Form_pg_opclass fc_opcForm;
				HeapTuple	fc_amTup;
				Form_pg_am	fc_amForm;
				char	   *fc_schema;

				fc_opcTup = SearchSysCache1(CLAOID,
										 ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_opcTup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for opclass %u",
							 fc_object->objectId);
					break;
				}
				fc_opcForm = (Form_pg_opclass) GETSTRUCT(fc_opcTup);
				fc_schema = get_namespace_name_or_temp(fc_opcForm->opcnamespace);

				fc_amTup = SearchSysCache1(AMOID,
										ObjectIdGetDatum(fc_opcForm->opcmethod));
				if (!HeapTupleIsValid(fc_amTup))
					elog(ERROR, "cache lookup failed for access method %u",
						 fc_opcForm->opcmethod);
				fc_amForm = (Form_pg_am) GETSTRUCT(fc_amTup);

				appendStringInfo(&fc_buffer, "%s USING %s",
								 quote_qualified_identifier(fc_schema,
															NameStr(fc_opcForm->opcname)),
								 quote_identifier(NameStr(fc_amForm->amname)));
				if (fc_objname)
					*fc_objname = list_make3(pstrdup(NameStr(fc_amForm->amname)),
										  fc_schema,
										  pstrdup(NameStr(fc_opcForm->opcname)));

				ReleaseSysCache(fc_amTup);
				ReleaseSysCache(fc_opcTup);
				break;
			}

		case OCLASS_OPFAMILY:
			fc_getOpFamilyIdentity(&fc_buffer, fc_object->objectId, fc_objname,
								fc_missing_ok);
			break;

		case OCLASS_AM:
			{
				char	   *fc_amname;

				fc_amname = get_am_name(fc_object->objectId);
				if (!fc_amname)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for access method %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfoString(&fc_buffer, quote_identifier(fc_amname));
				if (fc_objname)
					*fc_objname = list_make1(fc_amname);
			}
			break;

		case OCLASS_AMOP:
			{
				Relation	fc_amopDesc;
				HeapTuple	fc_tup;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_amscan;
				Form_pg_amop fc_amopForm;
				StringInfoData fc_opfam;
				char	   *fc_ltype;
				char	   *fc_rtype;

				fc_amopDesc = table_open(AccessMethodOperatorRelationId,
									  AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_amop_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_amscan = systable_beginscan(fc_amopDesc, AccessMethodOperatorOidIndexId, true,
											NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_amscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for amop entry %u",
							 fc_object->objectId);

					systable_endscan(fc_amscan);
					table_close(fc_amopDesc, AccessShareLock);
					break;
				}

				fc_amopForm = (Form_pg_amop) GETSTRUCT(fc_tup);

				initStringInfo(&fc_opfam);
				fc_getOpFamilyIdentity(&fc_opfam, fc_amopForm->amopfamily, fc_objname,
									false);

				fc_ltype = format_type_be_qualified(fc_amopForm->amoplefttype);
				fc_rtype = format_type_be_qualified(fc_amopForm->amoprighttype);

				if (fc_objname)
				{
					*fc_objname = lappend(*fc_objname,
									   psprintf("%d", fc_amopForm->amopstrategy));
					*fc_objargs = list_make2(fc_ltype, fc_rtype);
				}

				appendStringInfo(&fc_buffer, "operator %d (%s, %s) of %s",
								 fc_amopForm->amopstrategy,
								 fc_ltype, fc_rtype, fc_opfam.data);

				pfree(fc_opfam.data);

				systable_endscan(fc_amscan);
				table_close(fc_amopDesc, AccessShareLock);
				break;
			}

		case OCLASS_AMPROC:
			{
				Relation	fc_amprocDesc;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_amscan;
				HeapTuple	fc_tup;
				Form_pg_amproc fc_amprocForm;
				StringInfoData fc_opfam;
				char	   *fc_ltype;
				char	   *fc_rtype;

				fc_amprocDesc = table_open(AccessMethodProcedureRelationId,
										AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_amproc_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_amscan = systable_beginscan(fc_amprocDesc, AccessMethodProcedureOidIndexId, true,
											NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_amscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for amproc entry %u",
							 fc_object->objectId);

					systable_endscan(fc_amscan);
					table_close(fc_amprocDesc, AccessShareLock);
					break;
				}

				fc_amprocForm = (Form_pg_amproc) GETSTRUCT(fc_tup);

				initStringInfo(&fc_opfam);
				fc_getOpFamilyIdentity(&fc_opfam, fc_amprocForm->amprocfamily, fc_objname,
									false);

				fc_ltype = format_type_be_qualified(fc_amprocForm->amproclefttype);
				fc_rtype = format_type_be_qualified(fc_amprocForm->amprocrighttype);

				if (fc_objname)
				{
					*fc_objname = lappend(*fc_objname,
									   psprintf("%d", fc_amprocForm->amprocnum));
					*fc_objargs = list_make2(fc_ltype, fc_rtype);
				}

				appendStringInfo(&fc_buffer, "function %d (%s, %s) of %s",
								 fc_amprocForm->amprocnum,
								 fc_ltype, fc_rtype, fc_opfam.data);

				pfree(fc_opfam.data);

				systable_endscan(fc_amscan);
				table_close(fc_amprocDesc, AccessShareLock);
				break;
			}

		case OCLASS_REWRITE:
			{
				Relation	fc_ruleDesc;
				HeapTuple	fc_tup;
				Form_pg_rewrite fc_rule;

				fc_ruleDesc = table_open(RewriteRelationId, AccessShareLock);

				fc_tup = get_catalog_object_by_oid(fc_ruleDesc, Anum_pg_rewrite_oid,
												fc_object->objectId);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for rule %u",
							 fc_object->objectId);

					table_close(fc_ruleDesc, AccessShareLock);
					break;
				}

				fc_rule = (Form_pg_rewrite) GETSTRUCT(fc_tup);

				appendStringInfo(&fc_buffer, "%s on ",
								 quote_identifier(NameStr(fc_rule->rulename)));
				fc_getRelationIdentity(&fc_buffer, fc_rule->ev_class, fc_objname, false);
				if (fc_objname)
					*fc_objname = lappend(*fc_objname, pstrdup(NameStr(fc_rule->rulename)));

				table_close(fc_ruleDesc, AccessShareLock);
				break;
			}

		case OCLASS_TRIGGER:
			{
				Relation	fc_trigDesc;
				HeapTuple	fc_tup;
				Form_pg_trigger fc_trig;

				fc_trigDesc = table_open(TriggerRelationId, AccessShareLock);

				fc_tup = get_catalog_object_by_oid(fc_trigDesc, Anum_pg_trigger_oid,
												fc_object->objectId);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for trigger %u",
							 fc_object->objectId);

					table_close(fc_trigDesc, AccessShareLock);
					break;
				}

				fc_trig = (Form_pg_trigger) GETSTRUCT(fc_tup);

				appendStringInfo(&fc_buffer, "%s on ",
								 quote_identifier(NameStr(fc_trig->tgname)));
				fc_getRelationIdentity(&fc_buffer, fc_trig->tgrelid, fc_objname, false);
				if (fc_objname)
					*fc_objname = lappend(*fc_objname, pstrdup(NameStr(fc_trig->tgname)));

				table_close(fc_trigDesc, AccessShareLock);
				break;
			}

		case OCLASS_SCHEMA:
			{
				char	   *fc_nspname;

				fc_nspname = get_namespace_name_or_temp(fc_object->objectId);
				if (!fc_nspname)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for namespace %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfoString(&fc_buffer,
									   quote_identifier(fc_nspname));
				if (fc_objname)
					*fc_objname = list_make1(fc_nspname);
				break;
			}

		case OCLASS_STATISTIC_EXT:
			{
				HeapTuple	fc_tup;
				Form_pg_statistic_ext fc_formStatistic;
				char	   *fc_schema;

				fc_tup = SearchSysCache1(STATEXTOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for statistics object %u",
							 fc_object->objectId);
					break;
				}
				fc_formStatistic = (Form_pg_statistic_ext) GETSTRUCT(fc_tup);
				fc_schema = get_namespace_name_or_temp(fc_formStatistic->stxnamespace);
				appendStringInfoString(&fc_buffer,
									   quote_qualified_identifier(fc_schema,
																  NameStr(fc_formStatistic->stxname)));
				if (fc_objname)
					*fc_objname = list_make2(fc_schema,
										  pstrdup(NameStr(fc_formStatistic->stxname)));
				ReleaseSysCache(fc_tup);
			}
			break;

		case OCLASS_TSPARSER:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_parser fc_formParser;
				char	   *fc_schema;

				fc_tup = SearchSysCache1(TSPARSEROID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search parser %u",
							 fc_object->objectId);
					break;
				}
				fc_formParser = (Form_pg_ts_parser) GETSTRUCT(fc_tup);
				fc_schema = get_namespace_name_or_temp(fc_formParser->prsnamespace);
				appendStringInfoString(&fc_buffer,
									   quote_qualified_identifier(fc_schema,
																  NameStr(fc_formParser->prsname)));
				if (fc_objname)
					*fc_objname = list_make2(fc_schema,
										  pstrdup(NameStr(fc_formParser->prsname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_TSDICT:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_dict fc_formDict;
				char	   *fc_schema;

				fc_tup = SearchSysCache1(TSDICTOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search dictionary %u",
							 fc_object->objectId);
					break;
				}
				fc_formDict = (Form_pg_ts_dict) GETSTRUCT(fc_tup);
				fc_schema = get_namespace_name_or_temp(fc_formDict->dictnamespace);
				appendStringInfoString(&fc_buffer,
									   quote_qualified_identifier(fc_schema,
																  NameStr(fc_formDict->dictname)));
				if (fc_objname)
					*fc_objname = list_make2(fc_schema,
										  pstrdup(NameStr(fc_formDict->dictname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_TSTEMPLATE:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_template fc_formTmpl;
				char	   *fc_schema;

				fc_tup = SearchSysCache1(TSTEMPLATEOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search template %u",
							 fc_object->objectId);
					break;
				}
				fc_formTmpl = (Form_pg_ts_template) GETSTRUCT(fc_tup);
				fc_schema = get_namespace_name_or_temp(fc_formTmpl->tmplnamespace);
				appendStringInfoString(&fc_buffer,
									   quote_qualified_identifier(fc_schema,
																  NameStr(fc_formTmpl->tmplname)));
				if (fc_objname)
					*fc_objname = list_make2(fc_schema,
										  pstrdup(NameStr(fc_formTmpl->tmplname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_TSCONFIG:
			{
				HeapTuple	fc_tup;
				Form_pg_ts_config fc_formCfg;
				char	   *fc_schema;

				fc_tup = SearchSysCache1(TSCONFIGOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for text search configuration %u",
							 fc_object->objectId);
					break;
				}
				fc_formCfg = (Form_pg_ts_config) GETSTRUCT(fc_tup);
				fc_schema = get_namespace_name_or_temp(fc_formCfg->cfgnamespace);
				appendStringInfoString(&fc_buffer,
									   quote_qualified_identifier(fc_schema,
																  NameStr(fc_formCfg->cfgname)));
				if (fc_objname)
					*fc_objname = list_make2(fc_schema,
										  pstrdup(NameStr(fc_formCfg->cfgname)));
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_ROLE:
			{
				char	   *fc_username;

				fc_username = GetUserNameFromId(fc_object->objectId, fc_missing_ok);
				if (!fc_username)
					break;
				if (fc_objname)
					*fc_objname = list_make1(fc_username);
				appendStringInfoString(&fc_buffer,
									   quote_identifier(fc_username));
				break;
			}

		case OCLASS_DATABASE:
			{
				char	   *fc_datname;

				fc_datname = get_database_name(fc_object->objectId);
				if (!fc_datname)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for database %u",
							 fc_object->objectId);
					break;
				}
				if (fc_objname)
					*fc_objname = list_make1(fc_datname);
				appendStringInfoString(&fc_buffer,
									   quote_identifier(fc_datname));
				break;
			}

		case OCLASS_TBLSPACE:
			{
				char	   *fc_tblspace;

				fc_tblspace = get_tablespace_name(fc_object->objectId);
				if (!fc_tblspace)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for tablespace %u",
							 fc_object->objectId);
					break;
				}
				if (fc_objname)
					*fc_objname = list_make1(fc_tblspace);
				appendStringInfoString(&fc_buffer,
									   quote_identifier(fc_tblspace));
				break;
			}

		case OCLASS_FDW:
			{
				ForeignDataWrapper *fc_fdw;

				fc_fdw = GetForeignDataWrapperExtended(fc_object->objectId,
													fc_missing_ok);
				if (fc_fdw)
				{
					appendStringInfoString(&fc_buffer, quote_identifier(fc_fdw->fdwname));
					if (fc_objname)
						*fc_objname = list_make1(pstrdup(fc_fdw->fdwname));
				}
				break;
			}

		case OCLASS_FOREIGN_SERVER:
			{
				ForeignServer *fc_srv;

				fc_srv = GetForeignServerExtended(fc_object->objectId,
											   fc_missing_ok);
				if (fc_srv)
				{
					appendStringInfoString(&fc_buffer,
										   quote_identifier(fc_srv->servername));
					if (fc_objname)
						*fc_objname = list_make1(pstrdup(fc_srv->servername));
				}
				break;
			}

		case OCLASS_USER_MAPPING:
			{
				HeapTuple	fc_tup;
				Oid			fc_useid;
				Form_pg_user_mapping fc_umform;
				ForeignServer *fc_srv;
				const char *fc_usename;

				fc_tup = SearchSysCache1(USERMAPPINGOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for user mapping %u",
							 fc_object->objectId);
					break;
				}
				fc_umform = (Form_pg_user_mapping) GETSTRUCT(fc_tup);
				fc_useid = fc_umform->umuser;
				fc_srv = GetForeignServer(fc_umform->umserver);

				ReleaseSysCache(fc_tup);

				if (OidIsValid(fc_useid))
					fc_usename = GetUserNameFromId(fc_useid, false);
				else
					fc_usename = "public";

				if (fc_objname)
				{
					*fc_objname = list_make1(pstrdup(fc_usename));
					*fc_objargs = list_make1(pstrdup(fc_srv->servername));
				}

				appendStringInfo(&fc_buffer, "%s on server %s",
								 quote_identifier(fc_usename),
								 fc_srv->servername);
				break;
			}

		case OCLASS_DEFACL:
			{
				Relation	fc_defaclrel;
				ScanKeyData fc_skey[1];
				SysScanDesc fc_rcscan;
				HeapTuple	fc_tup;
				Form_pg_default_acl fc_defacl;
				char	   *fc_schema;
				char	   *fc_username;

				fc_defaclrel = table_open(DefaultAclRelationId, AccessShareLock);

				ScanKeyInit(&fc_skey[0],
							Anum_pg_default_acl_oid,
							BTEqualStrategyNumber, F_OIDEQ,
							ObjectIdGetDatum(fc_object->objectId));

				fc_rcscan = systable_beginscan(fc_defaclrel, DefaultAclOidIndexId,
											true, NULL, 1, fc_skey);

				fc_tup = systable_getnext(fc_rcscan);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for default ACL %u",
							 fc_object->objectId);

					systable_endscan(fc_rcscan);
					table_close(fc_defaclrel, AccessShareLock);
					break;
				}

				fc_defacl = (Form_pg_default_acl) GETSTRUCT(fc_tup);

				fc_username = GetUserNameFromId(fc_defacl->defaclrole, false);
				appendStringInfo(&fc_buffer,
								 "for role %s",
								 quote_identifier(fc_username));

				if (OidIsValid(fc_defacl->defaclnamespace))
				{
					fc_schema = get_namespace_name_or_temp(fc_defacl->defaclnamespace);
					appendStringInfo(&fc_buffer,
									 " in schema %s",
									 quote_identifier(fc_schema));
				}
				else
					fc_schema = NULL;

				switch (fc_defacl->defaclobjtype)
				{
					case DEFACLOBJ_RELATION:
						appendStringInfoString(&fc_buffer,
											   " on tables");
						break;
					case DEFACLOBJ_SEQUENCE:
						appendStringInfoString(&fc_buffer,
											   " on sequences");
						break;
					case DEFACLOBJ_FUNCTION:
						appendStringInfoString(&fc_buffer,
											   " on functions");
						break;
					case DEFACLOBJ_TYPE:
						appendStringInfoString(&fc_buffer,
											   " on types");
						break;
					case DEFACLOBJ_NAMESPACE:
						appendStringInfoString(&fc_buffer,
											   " on schemas");
						break;
				}

				if (fc_objname)
				{
					*fc_objname = list_make1(fc_username);
					if (fc_schema)
						*fc_objname = lappend(*fc_objname, fc_schema);
					*fc_objargs = list_make1(psprintf("%c", fc_defacl->defaclobjtype));
				}

				systable_endscan(fc_rcscan);
				table_close(fc_defaclrel, AccessShareLock);
				break;
			}

		case OCLASS_EXTENSION:
			{
				char	   *fc_extname;

				fc_extname = get_extension_name(fc_object->objectId);
				if (!fc_extname)
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for extension %u",
							 fc_object->objectId);
					break;
				}
				appendStringInfoString(&fc_buffer, quote_identifier(fc_extname));
				if (fc_objname)
					*fc_objname = list_make1(fc_extname);
				break;
			}

		case OCLASS_EVENT_TRIGGER:
			{
				HeapTuple	fc_tup;
				Form_pg_event_trigger fc_trigForm;
				char	   *fc_evtname;

				fc_tup = SearchSysCache1(EVENTTRIGGEROID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for event trigger %u",
							 fc_object->objectId);
					break;
				}
				fc_trigForm = (Form_pg_event_trigger) GETSTRUCT(fc_tup);
				fc_evtname = pstrdup(NameStr(fc_trigForm->evtname));
				appendStringInfoString(&fc_buffer, quote_identifier(fc_evtname));
				if (fc_objname)
					*fc_objname = list_make1(fc_evtname);
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_PARAMETER_ACL:
			{
				HeapTuple	fc_tup;
				Datum		fc_nameDatum;
				bool		fc_isNull;
				char	   *fc_parname;

				fc_tup = SearchSysCache1(PARAMETERACLOID,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for parameter ACL %u",
							 fc_object->objectId);
					break;
				}
				fc_nameDatum = SysCacheGetAttr(PARAMETERACLOID, fc_tup,
											Anum_pg_parameter_acl_parname,
											&fc_isNull);
				Assert(!fc_isNull);
				fc_parname = TextDatumGetCString(fc_nameDatum);
				appendStringInfoString(&fc_buffer, fc_parname);
				if (fc_objname)
					*fc_objname = list_make1(fc_parname);
				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_POLICY:
			{
				Relation	fc_polDesc;
				HeapTuple	fc_tup;
				Form_pg_policy fc_policy;

				fc_polDesc = table_open(PolicyRelationId, AccessShareLock);

				fc_tup = get_catalog_object_by_oid(fc_polDesc, Anum_pg_policy_oid,
												fc_object->objectId);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for policy %u",
							 fc_object->objectId);

					table_close(fc_polDesc, AccessShareLock);
					break;
				}

				fc_policy = (Form_pg_policy) GETSTRUCT(fc_tup);

				appendStringInfo(&fc_buffer, "%s on ",
								 quote_identifier(NameStr(fc_policy->polname)));
				fc_getRelationIdentity(&fc_buffer, fc_policy->polrelid, fc_objname, false);
				if (fc_objname)
					*fc_objname = lappend(*fc_objname, pstrdup(NameStr(fc_policy->polname)));

				table_close(fc_polDesc, AccessShareLock);
				break;
			}

		case OCLASS_PUBLICATION:
			{
				char	   *fc_pubname;

				fc_pubname = get_publication_name(fc_object->objectId, fc_missing_ok);
				if (fc_pubname)
				{
					appendStringInfoString(&fc_buffer,
										   quote_identifier(fc_pubname));
					if (fc_objname)
						*fc_objname = list_make1(fc_pubname);
				}
				break;
			}

		case OCLASS_PUBLICATION_NAMESPACE:
			{
				char	   *fc_pubname;
				char	   *fc_nspname;

				if (!fc_getPublicationSchemaInfo(fc_object, fc_missing_ok, &fc_pubname,
											  &fc_nspname))
					break;
				appendStringInfo(&fc_buffer, "%s in publication %s",
								 fc_nspname, fc_pubname);

				if (fc_objargs)
					*fc_objargs = list_make1(fc_pubname);
				else
					pfree(fc_pubname);

				if (fc_objname)
					*fc_objname = list_make1(fc_nspname);
				else
					pfree(fc_nspname);

				break;
			}

		case OCLASS_PUBLICATION_REL:
			{
				HeapTuple	fc_tup;
				char	   *fc_pubname;
				Form_pg_publication_rel fc_prform;

				fc_tup = SearchSysCache1(PUBLICATIONREL,
									  ObjectIdGetDatum(fc_object->objectId));
				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "cache lookup failed for publication table %u",
							 fc_object->objectId);
					break;
				}

				fc_prform = (Form_pg_publication_rel) GETSTRUCT(fc_tup);
				fc_pubname = get_publication_name(fc_prform->prpubid, false);

				fc_getRelationIdentity(&fc_buffer, fc_prform->prrelid, fc_objname, false);
				appendStringInfo(&fc_buffer, " in publication %s", fc_pubname);

				if (fc_objargs)
					*fc_objargs = list_make1(fc_pubname);

				ReleaseSysCache(fc_tup);
				break;
			}

		case OCLASS_SUBSCRIPTION:
			{
				char	   *fc_subname;

				fc_subname = get_subscription_name(fc_object->objectId, fc_missing_ok);
				if (fc_subname)
				{
					appendStringInfoString(&fc_buffer,
										   quote_identifier(fc_subname));
					if (fc_objname)
						*fc_objname = list_make1(fc_subname);
				}
				break;
			}

		case OCLASS_TRANSFORM:
			{
				Relation	fc_transformDesc;
				HeapTuple	fc_tup;
				Form_pg_transform fc_transform;
				char	   *fc_transformLang;
				char	   *fc_transformType;

				fc_transformDesc = table_open(TransformRelationId, AccessShareLock);

				fc_tup = get_catalog_object_by_oid(fc_transformDesc,
												Anum_pg_transform_oid,
												fc_object->objectId);

				if (!HeapTupleIsValid(fc_tup))
				{
					if (!fc_missing_ok)
						elog(ERROR, "could not find tuple for transform %u",
							 fc_object->objectId);

					table_close(fc_transformDesc, AccessShareLock);
					break;
				}

				fc_transform = (Form_pg_transform) GETSTRUCT(fc_tup);

				fc_transformType = format_type_be_qualified(fc_transform->trftype);
				fc_transformLang = get_language_name(fc_transform->trflang, false);

				appendStringInfo(&fc_buffer, "for %s on language %s",
								 fc_transformType,
								 fc_transformLang);
				if (fc_objname)
				{
					*fc_objname = list_make1(fc_transformType);
					*fc_objargs = list_make1(pstrdup(fc_transformLang));
				}

				table_close(fc_transformDesc, AccessShareLock);
			}
			break;

			/*
			 * 这里故意没有默认 case；我们希望编译器
			 * 在上面未处理新 OCLASS 时发出警告。
			 */
	}

	if (!fc_missing_ok)
	{
		/*
		 * 如果请求了get_object_address()的表示，确保我们提供了
		 * 一个。我们不检查objargs，因为上述许多案例将其保持为NIL。
		 */
		if (fc_objname && *fc_objname == NIL)
			elog(ERROR, "requested object address for unsupported object class %d: text result \"%s\"",
				 (int) getObjectClass(fc_object), fc_buffer.data);
	}
	else
	{
		/* 空缓冲区相当于未找到对象 */
		if (fc_buffer.len == 0)
		{
			Assert((fc_objname == NULL || *fc_objname == NIL) &&
				   (fc_objargs == NULL || *fc_objargs == NIL));
			return NULL;
		}
	}

	return fc_buffer.data;
}

static void fc_getOpFamilyIdentity(StringInfo fc_buffer, Oid fc_opfid, List **fc_object,
					bool fc_missing_ok)
{
	HeapTuple	fc_opfTup;
	Form_pg_opfamily fc_opfForm;
	HeapTuple	fc_amTup;
	Form_pg_am	fc_amForm;
	char	   *fc_schema;

	fc_opfTup = SearchSysCache1(OPFAMILYOID, ObjectIdGetDatum(fc_opfid));
	if (!HeapTupleIsValid(fc_opfTup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for opfamily %u", fc_opfid);
		return;
	}
	fc_opfForm = (Form_pg_opfamily) GETSTRUCT(fc_opfTup);

	fc_amTup = SearchSysCache1(AMOID, ObjectIdGetDatum(fc_opfForm->opfmethod));
	if (!HeapTupleIsValid(fc_amTup))
		elog(ERROR, "cache lookup failed for access method %u",
			 fc_opfForm->opfmethod);
	fc_amForm = (Form_pg_am) GETSTRUCT(fc_amTup);

	fc_schema = get_namespace_name_or_temp(fc_opfForm->opfnamespace);
	appendStringInfo(fc_buffer, "%s USING %s",
					 quote_qualified_identifier(fc_schema,
												NameStr(fc_opfForm->opfname)),
					 NameStr(fc_amForm->amname));

	if (fc_object)
		*fc_object = list_make3(pstrdup(NameStr(fc_amForm->amname)),
							 pstrdup(fc_schema),
							 pstrdup(NameStr(fc_opfForm->opfname)));

	ReleaseSysCache(fc_amTup);
	ReleaseSysCache(fc_opfTup);
}

/*
 * 将关系标识（带引号的限定名称）附加到给定的
 * StringInfo。
 */
static void fc_getRelationIdentity(StringInfo fc_buffer, Oid fc_relid, List **fc_object,
					bool fc_missing_ok)
{
	HeapTuple	fc_relTup;
	Form_pg_class fc_relForm;
	char	   *fc_schema;

	fc_relTup = SearchSysCache1(RELOID,
							 ObjectIdGetDatum(fc_relid));
	if (!HeapTupleIsValid(fc_relTup))
	{
		if (!fc_missing_ok)
			elog(ERROR, "cache lookup failed for relation %u", fc_relid);

		if (fc_object)
			*fc_object = NIL;
		return;
	}
	fc_relForm = (Form_pg_class) GETSTRUCT(fc_relTup);

	fc_schema = get_namespace_name_or_temp(fc_relForm->relnamespace);
	appendStringInfoString(fc_buffer,
						   quote_qualified_identifier(fc_schema,
													  NameStr(fc_relForm->relname)));
	if (fc_object)
		*fc_object = list_make2(fc_schema, pstrdup(NameStr(fc_relForm->relname)));

	ReleaseSysCache(fc_relTup);
}

/*
 * 辅助函数，从C字符串列表构建一个TEXT数组。
 */
ArrayType * strlist_to_textarray(List *fc_list)
{
	ArrayType  *fc_arr;
	Datum	   *fc_datums;
	bool	   *fc_nulls;
	int			fc_j = 0;
	ListCell   *fc_cell;
	MemoryContext fc_memcxt;
	MemoryContext fc_oldcxt;
	int			fc_lb[1];

	/* 在临时上下文中工作；比逐个pfree Datums更容易 */
	fc_memcxt = AllocSetContextCreate(CurrentMemoryContext,
								   "strlist to array",
								   ALLOCSET_DEFAULT_SIZES);
	fc_oldcxt = MemoryContextSwitchTo(fc_memcxt);

	fc_datums = (Datum *) palloc(sizeof(Datum) * list_length(fc_list));
	fc_nulls = palloc(sizeof(bool) * list_length(fc_list));

	foreach(fc_cell, fc_list)
	{
		char	   *fc_name = lfirst(fc_cell);

		if (fc_name)
		{
			fc_nulls[fc_j] = false;
			fc_datums[fc_j++] = CStringGetTextDatum(fc_name);
		}
		else
			fc_nulls[fc_j] = true;
	}

	MemoryContextSwitchTo(fc_oldcxt);

	fc_lb[0] = 1;
	fc_arr = construct_md_array(fc_datums, fc_nulls, 1, &fc_j,
							 fc_lb, TEXTOID, -1, false, TYPALIGN_INT);

	MemoryContextDelete(fc_memcxt);

	return fc_arr;
}

/*
 * get_relkind_objtype
 *
 * 返回调用者给定的relkind的对象类型。
 *
 * 如果传递了意外的relkind，我们会说OBJECT_TABLE而不是
 * 失败。这是因为这主要用于生成关系上失败的ACL检查
 * 的错误消息，而我们宁愿生成一条通用消息说"table"而不是
 * 完全失败。
 */
ObjectType
get_relkind_objtype(char fc_relkind)
{
	switch (fc_relkind)
	{
		case RELKIND_RELATION:
		case RELKIND_PARTITIONED_TABLE:
			return OBJECT_TABLE;
		case RELKIND_INDEX:
		case RELKIND_PARTITIONED_INDEX:
			return OBJECT_INDEX;
		case RELKIND_SEQUENCE:
			return OBJECT_SEQUENCE;
		case RELKIND_VIEW:
			return OBJECT_VIEW;
		case RELKIND_MATVIEW:
			return OBJECT_MATVIEW;
		case RELKIND_FOREIGN_TABLE:
			return OBJECT_FOREIGN_TABLE;
		case RELKIND_TOASTVALUE:
			return OBJECT_TABLE;
		default:
			/* 根据上述内容，不要引发错误 */
			return OBJECT_TABLE;
	}
}
